spidev的使用(SPI用户态API)

参考资料:

内核驱动:drivers\spi\spidev.c
内核提供的测试程序:tools\spi\spidev_fdx.c
内核文档:Documentation\spi\spidev

一、spidev驱动程序分析

内核驱动:drivers\spi\spidev.c

1.1 驱动框架

在这里插入图片描述
设备树示例:

spidev0: spidev@0 {
	compatible = "spidev";
	reg = <0>;
	spi-max-frequency = <50000000>;
};

设备树里某个spi设备节点的compatible属性等于下列值,就会跟spidev驱动匹配:

  • “rohm,dh2228fv”
  • “lineartechnology,ltc2488”
  • “spidev”
    匹配后,spidev.c的spidev_probe会被调用,它会:
  • 分配一个spidev_data结构体,用来记录对于的spi_device
  • spidev_data会被记录在一个链表里
  • 分配一个此设备号,以后可以根据这个次设备号在链表里找到spidev_data
  • device_create:这会生产一个设备节点/dev/spidevB.D,B表示总线号,D表示它是这个SPI Master下第几个设备
    以后,我们就可以通过/dev/spidevB.D来访问spidev驱动程序

1.2 驱动程序分析:

static struct spi_driver spidev_spi_driver = {
    .driver = {
        .name =     "spidev",
        .of_match_table = of_match_ptr(spidev_dt_ids),
        .acpi_match_table = ACPI_PTR(spidev_acpi_ids),
    },
    .probe =    spidev_probe,
    .remove =   spidev_remove,
};

static int __init spidev_init(void)
{
    int status;

    BUILD_BUG_ON(N_SPI_MINORS > 256);
    status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);	//注册spidev_fops
		//...
    spidev_class = class_create(THIS_MODULE, "spidev");		//创建spidev类
		//...
    status = spi_register_driver(&spidev_spi_driver);		//注册spi_driver
		//...
    return status;
}
module_init(spidev_init);


static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;

	//....
	spidev_probe_acpi(spi);

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);		//分配结构体
	//...

	/* Initialize the driver data */
	spidev->spi = spi;						//1. spidev_data里记录spi_device结构体
	//...
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);		//2. 找到一个空闲的次设备号
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		dev = device_create(spidev_class, &spi->dev, spidev->devt,		//3. 创建一个设备,通过/dev/spidevx.x
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);		//spi的第几个spi_master设备,spi的片选信号信息
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		//....
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);		//4. 这个spidev_data会放入device_list链表中
		//app调用/dev/spidevx.x时,通过次设备号找到spidev_data结构体,从而找到spi_device。
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	//....

	return status;
}

spidev.c通过file_operation向App提供接口:

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.write =	spidev_write,		//读写的单工模式
	.read =		spidev_read,		
	.unlocked_ioctl = spidev_ioctl,		//设置频率、模式,进行双工传输(同时读写)
	.compat_ioctl = spidev_compat_ioctl,
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	no_llseek,
};

static int spidev_open(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int			status = -ENXIO;

	mutex_lock(&device_list_lock);
	//1. 在链表中寻找和inode下的注册的次设备号的
	list_for_each_entry(spidev, &device_list, device_entry) {
		if (spidev->devt == inode->i_rdev) {
			status = 0;
			break;
		}
	}
	//...

	spidev->users++;
	filp->private_data = spidev;		//2.把找到的spidev_data保存在私有数据中
	nonseekable_open(inode, filp);
	//....
}

1.2.1 读函数

static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	//...
	spidev = filp->private_data;		//从私有数据中获取spidev_data数据

	mutex_lock(&spidev->buf_lock);
	status = spidev_sync_read(spidev, count);		//1.读数据
	if (status > 0) {
		unsigned long	missing;

		missing = copy_to_user(buf, spidev->rx_buffer, status);		//2.copy_to_user
		if (missing == status)
			status = -EFAULT;
		else
			status = status - missing;
	}
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= spidev->rx_buffer,		//2.指定了rx_buffer
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;			//1.构造一个message
	//3. 发起传输
	spi_message_init(&m);			//初始化spi_message
	spi_message_add_tail(&t, &m);	//把transfer放入message中
	return spidev_sync(spidev, &m);		//发起传输
}


1.2.2 写函数

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count);
	if (missing == 0)
		status = spidev_sync_write(spidev, count);		//调用函数去写操作
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,		//2.指定tx_buffer
			.len		= len,		//指定长度
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;		//1.构造一个消息
	//3.初始化消息,把t放到message的尾部
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);	//4.进行SPI的同步操作
}

1.2.3 通过ioctl读写参数

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			retval = 0;
	struct spidev_data	*spidev;
	struct spi_device	*spi;
	u32			tmp;
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;

	/* Check type and command number */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
		return -ENOTTY;

	/* guard against device removal before, or while,
	 * we issue this ioctl.
	 */
	spidev = filp->private_data;
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		return -ESHUTDOWN;

	/* use the buffer lock here for triple duty:
	 *  - prevent I/O (from us) so calling spi_setup() is safe;
	 *  - prevent concurrent SPI_IOC_WR_* from morphing
	 *    data fields while SPI_IOC_RD_* reads them;
	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
	 */
	mutex_lock(&spidev->buf_lock);

	switch (cmd) {
	/* read requests */
	case SPI_IOC_RD_MODE:
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MODE32:
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u32 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:
		retval = put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:
		retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:
		retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
		break;

	/* write requests */
	case SPI_IOC_WR_MODE:
	case SPI_IOC_WR_MODE32:
		if (cmd == SPI_IOC_WR_MODE)
			retval = get_user(tmp, (u8 __user *)arg);
		else
			retval = get_user(tmp, (u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;
			}

			tmp |= spi->mode & ~SPI_MODE_MASK;
			spi->mode = (u16)tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp)
				spi->mode |= SPI_LSB_FIRST;
			else
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "%csb first\n",
						tmp ? 'l' : 'm');
		}
		break;
	case SPI_IOC_WR_BITS_PER_WORD:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->bits_per_word;

			spi->bits_per_word = tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->bits_per_word = save;
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->max_speed_hz;

			spi->max_speed_hz = tmp;
			retval = spi_setup(spi);
			if (retval >= 0)
				spidev->speed_hz = tmp;
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
			spi->max_speed_hz = save;
		}
		break;

	default:
		/* segmented and/or full-duplex I/O request */
		/* Check message and copy into scratch area */
		ioc = spidev_get_ioc_message(cmd,
				(struct spi_ioc_transfer __user *)arg, &n_ioc);
		if (IS_ERR(ioc)) {
			retval = PTR_ERR(ioc);
			break;
		}
		if (!ioc)
			break;	/* n_ioc is also 0 */

		/* translate to spi_message, execute */
		retval = spidev_message(spidev, ioc, n_ioc);
		kfree(ioc);
		break;
	}

	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);
	return retval;
}

二、spidev应用程序分析

内核提供的测试程序:tools\spi\spidev_fdx.c

2.1 使用方法

spidev_fdx [-h] [-m N] [ -r N] /dev/spidevB.D

  • -h:打印用法
  • -m N:先写1个字节0xaa,再读N个字节,注意:不是同时写同时读
  • -r N:读N个字节

2.2 代码分析

2.2.1 显示设备属性:

static void dumpstat(const char *name, int fd)
{
	__u8	lsb, bits;
	__u32	mode, speed;

	if (ioctl(fd, SPI_IOC_RD_MODE32, &mode) < 0) {
		perror("SPI rd_mode");
		return;
	}
	if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {
		perror("SPI rd_lsb_fist");
		return;
	}
	if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {
		perror("SPI bits_per_word");
		return;
	}
	if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {
		perror("SPI max_speed_hz");
		return;
	}

	printf("%s: spi mode 0x%x, %d bits %sper word, %d Hz max\n",
			name, mode, bits, lsb ? "(lsb first) " : "", speed);
}

2.2.2 读数据

static void do_read(int fd, int len)
{
	unsigned char	buf[32], *bp;
	int		status;

	/* read at least 2 bytes, no more than 32 */
	if (len < 2)
		len = 2;
	else if (len > sizeof(buf))
		len = sizeof(buf);
	memset(buf, 0, sizeof buf);

	status = read(fd, buf, len);		//读取数据
	if (status < 0) {
		perror("read");
		return;
	}
	if (status != len) {
		fprintf(stderr, "short read\n");
		return;
	}

	printf("read(%2d, %2d): %02x %02x,", len, status,
		buf[0], buf[1]);
	status -= 2;
	bp = buf + 2;
	while (status-- > 0)
		printf(" %02x", *bp++);
	printf("\n");
}

2.2.3 先读再写

static void do_msg(int fd, int len)
{
	struct spi_ioc_transfer	xfer[2];
	unsigned char		buf[32], *bp;
	int			status;

	memset(xfer, 0, sizeof xfer);
	memset(buf, 0, sizeof buf);

	if (len > sizeof buf)
		len = sizeof buf;
//1.先写
	buf[0] = 0xaa;
	xfer[0].tx_buf = (unsigned long)buf;
	xfer[0].len = 1;
//2.后读
	xfer[1].rx_buf = (unsigned long) buf;
	xfer[1].len = len;

	status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}

	printf("response(%2d, %2d): ", len, status);
	for (bp = buf; len; len--)
		printf(" %02x", *bp++);
	printf("\n");
}

2.2.4 同时读写

static void do_msg(int fd, int len)
{
	struct spi_ioc_transfer	xfer[2];
	unsigned char		buf[32], *bp;
	unsigned char		buf_rx[32], *bp;
	int			status;

	memset(xfer, 0, sizeof xfer);
	memset(buf, 0, sizeof buf);

	if (len > sizeof buf)
		len = sizeof buf;
//设置同一个xfer的tx_buf,rx_buf即可同时读写
	buf[0] = 0xaa;
	xfer[0].tx_buf = (unsigned long)buf;
	xfer[0].rx_buf = (unsigned long)buf_rx;
	xfer[0].len = 1en;

	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}

	printf("response(%2d, %2d): ", len, status);
	for (bp = buf; len; len--)
		printf(" %02x", *bp++);
	printf("\n");
}

三、spidev的缺点

使用read、write函数时,只能读、写,之二十半双工方式
使用ioctl可以达到全双工的读写
但是spidev有2个缺点:

  • 不支持中断
  • 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回
  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
以下是使用Intel Serial IO SPI接口的API函数实现SPI通信的代码示例,仅供参考: ```C++ #include <windows.h> #include <stdio.h> #include <initguid.h> #include <devguid.h> #include <setupapi.h> #include <serialio.h> #define SPI_TRANSFER_SIZE 16 int main() { HANDLE hSpiDevice = INVALID_HANDLE_VALUE; SERIAL_IO_SPI_TRANSFER SpiTransfer; SERIAL_IO_SPI_STATUS SpiStatus; DWORD dwBytesReturned; BYTE TxBuffer[SPI_TRANSFER_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}; BYTE RxBuffer[SPI_TRANSFER_SIZE]; // 查找Serial IO SPI设备 HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_SERIALIO_SPI, NULL, NULL, DIGCF_PRESENT); if (hDevInfo == INVALID_HANDLE_VALUE) { printf("Failed to get Serial IO SPI device information.\n"); return 1; } // 打开Serial IO SPI设备 SP_DEVICE_INTERFACE_DATA DevInterfaceData; DevInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); if (!SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVCLASS_SERIALIO_SPI, 0, &DevInterfaceData)) { printf("Failed to enumerate Serial IO SPI device interface.\n"); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } // 获取Serial IO SPI设备路径 DWORD dwRequiredSize; SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevInterfaceData, NULL, 0, &dwRequiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA pDevInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(dwRequiredSize); if (pDevInterfaceDetailData == NULL) { printf("Failed to allocate memory for Serial IO SPI device interface detail data.\n"); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } pDevInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevInterfaceData, pDevInterfaceDetailData, dwRequiredSize, NULL, NULL)) { printf("Failed to get Serial IO SPI device interface detail.\n"); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } hSpiDevice = CreateFile(pDevInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSpiDevice == INVALID_HANDLE_VALUE) { printf("Failed to open Serial IO SPI device.\n"); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } // 初始化Serial IO SPI设备 SERIAL_IO_SPI_INIT SpiInit; SpiInit.TransferWidth = SERIAL_IO_SPI_TRANSFER_WIDTH_8BIT; SpiInit.ClockFrequency = 1000000; SpiInit.TransferMode = SERIAL_IO_SPI_TRANSFER_MODE_0; SpiInit.DataOrder = SERIAL_IO_SPI_DATA_ORDER_MSB_FIRST; SpiInit.ClockPolarity = SERIAL_IO_SPI_CLOCK_POLARITY_LOW; SpiInit.ClockPhase = SERIAL_IO_SPI_CLOCK_PHASE_LEADING_EDGE; SpiInit.ChipSelectPolarity = SERIAL_IO_SPI_CHIP_SELECT_POLARITY_ACTIVE_LOW; SpiInit.ChipSelect = 0; if (!DeviceIoControl(hSpiDevice, IOCTL_SERIAL_IO_SPI_INITIALIZE, &SpiInit, sizeof(SERIAL_IO_SPI_INIT), NULL, 0, &dwBytesReturned, NULL)) { printf("Failed to initialize Serial IO SPI device.\n"); CloseHandle(hSpiDevice); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } // 发送SPI数据 SpiTransfer.DataBufferLength = SPI_TRANSFER_SIZE; SpiTransfer.pDataBuffer = TxBuffer; if (!DeviceIoControl(hSpiDevice, IOCTL_SERIAL_IO_SPI_TRANSFER, &SpiTransfer, sizeof(SERIAL_IO_SPI_TRANSFER), &SpiStatus, sizeof(SERIAL_IO_SPI_STATUS), &dwBytesReturned, NULL)) { printf("Failed to transfer SPI data.\n"); CloseHandle(hSpiDevice); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } // 接收SPI数据 SpiTransfer.DataBufferLength = SPI_TRANSFER_SIZE; SpiTransfer.pDataBuffer = RxBuffer; if (!DeviceIoControl(hSpiDevice, IOCTL_SERIAL_IO_SPI_TRANSFER, &SpiTransfer, sizeof(SERIAL_IO_SPI_TRANSFER), &SpiStatus, sizeof(SERIAL_IO_SPI_STATUS), &dwBytesReturned, NULL)) { printf("Failed to transfer SPI data.\n"); CloseHandle(hSpiDevice); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 1; } // 输出接收到的SPI数据 for (int i = 0; i < SPI_TRANSFER_SIZE; i++) { printf("%02X ", RxBuffer[i]); } printf("\n"); // 关闭Serial IO SPI设备 DeviceIoControl(hSpiDevice, IOCTL_SERIAL_IO_SPI_UNINITIALIZE, NULL, 0, NULL, 0, &dwBytesReturned, NULL); CloseHandle(hSpiDevice); free(pDevInterfaceDetailData); SetupDiDestroyDeviceInfoList(hDevInfo); return 0; } ``` 需要注意的是,以上代码示例仅供参考,具体的使用方法和步骤可能会因为硬件设备和软件环境的不同而有所变化。在使用Intel Serial IO SPI接口的API函数之前,需要仔细阅读英特尔公司提供的说明和文档,了解具体的API函数名称、参数和用法,以确保代码的正确性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值