spi 驱动一:spi基本结构和spidev文件系统

作者: 李云鹏(qqliyunpeng@sina.cn) 

版本号: 20170124 
更新时间: <--> 
原创时间: <2017-01-24> 
版权: 本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处.


1. spi 简介:


        spi 中包含时钟线(clk)、MOSI(主设备发送,从设备接收)、MISO(主设备接收,从设备发送)、片选(CS),有四种工作模式(下边有介绍),此篇文章中介绍的是半双工的spi,他一般应用在和传感器的数据交互。


driver/spi 文件下的文件:

spi-bitbang.c 和 spi-bitbang-txrx.h 是 通用的用IO口模拟spi

spidev.c:devfs形式的spi驱动,此部分内核文档说明了,是半双工形式,即同一时间 MISO MOSI 只有一个在运行。又叫3线制

spi-coldfire-qspi.c:

coldfire是Freescale公司在M68K基础上开发的微处理器芯片。

QSPI是Queued SPI的简写,是Motorola公司推出的SPI接口的扩展,比SPI应用更加广泛。使用该接口,用户可以一次性传输包含多达16个8位或16位数据的传输队列。一旦传输启动,直到传输结束,都不需要CPU干预,极大的提高了传输效率。该协议在ColdFire系列MCU得到广泛应用。

spi-davinci.c:

davinci是Ti推出的一类芯片,名字叫达芬奇处理器,达芬奇技术是一种数字图像、视频、语音、音频信号处理的新平台,一经推出,就受到热烈欢迎,以其为基础的应用开发层出不穷。

spi-bufferfly.c:

是AVR的bufferfly平台,它长的是下边的样子,至于它的具体的性能,看这里

spi-dw.c、spi-dw-mid.c、spi-dw-mmio.c、spi-dw-pci.c、pxa2xx_spi.c:

dw是designware的缩写,是美国新思科技科技公司(synopsys)的被SoC/ASIC设计者最钟爱的设计IP库和验证IP库。它包括一个独立于工艺的、经验证的、可综合的虚拟微架构的元件集合,包括逻辑、算术、存储和专用元件系列,超过140个模块。

spi-oc-tiny.c:

oc是opencores的缩写,opencores是开源硬件ip核的社区,它里边提供了设计好的fpag/asic的ip核,这里的这个文件就是针对他里边的spi驱动器设计的驱动。


2. 关键的结构:


在spi.h中定义了spi相关的结构体,首先是spi_device:

/* master 侧的从设备的代理 */
struct spi_device {
	struct device		dev;
	struct spi_master	*master;
	u32			max_speed_hz;
	u8			chip_select;
	u8			mode;
#define	SPI_CPHA	0x01			/* 时钟相位 */
#define	SPI_CPOL	0x02			/* 时钟极性 */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#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			/* SI/SO 信号共享 */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 就一个设备,不用片选 */
#define	SPI_READY	0x80			/* slave pulls low to pause */
	u8			bits_per_word;
	int			irq;
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE];
};

对mode的定义根据的是s3c2440芯片手册上的有关SPI TRANSFER FORMAT部分:

CPOL = 0,表示的是时钟线空闲电平时低电平

CPOL = 1,表示的是时钟线空闲电平时高电平

CPHA = 0,在sck的第一个跳变沿开始采样,从图中可以看到的是上升沿

CPHA = 1,在sck的第二个跳变沿开始采样,从图中可以看到的是下降沿

SPI_MODE_0:

SPI_MODE_1:

SPI_MODE_2:

SPI_MODE_3:

然后来看看spi_master:spi作为master的控制器

struct spi_master {
	struct device	dev;
	struct list_head list;

	s16 bus_num; // 如果是负数,则是动态分配,整数指定用哪个spi控制器/驱动(从0开始)
	u16 num_chipselect; // 片选信号的个数,也就是从设备的数量,从设别就事0~num_chipselect,可以有没有连接的从设备

	u16			dma_alignment; // 是不是要求DMA的buffer的对齐

	u16			mode_bits; // 控制器启动的模式位,被控制器驱动解析
	/* 驱动器的其他设置 */
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* 不能全双工 */
#define SPI_MASTER_NO_RX	BIT(1)		/* 不能读buffer */
#define SPI_MASTER_NO_TX	BIT(2)		/* 不能写buffer */

	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;

	int			(*setup)(struct spi_device *spi); // 时钟和spi模式的设置函数
	int			(*transfer)(struct spi_device *spi, // 用于将message加入到message队列中,此函数一般不需要用户自己设计,保持null即可
						struct spi_message *mesg);
	void			(*cleanup)(struct spi_device *spi);

	bool				queued;
	struct kthread_worker		kworker; // kthread_worker,看相应的章节
	struct task_struct		*kworker_task; // kthread_worker中使用的任务,看相应的章节
	struct kthread_work		pump_messages; // kthread_work,看相应的章节
	spinlock_t			queue_lock;
	struct list_head		queue; // spi_message队列头,结构看下边的图
	struct spi_message		*cur_msg; // 正在处理的spi_message
	bool				busy;
	bool				running;
	bool				rt;

	int (*prepare_transfer_hardware)(struct spi_master *master); // 准备传输前硬件初始化
	int (*transfer_one_message)(struct spi_master *master, // 传输的硬件层的实现
				    struct spi_message *mesg);
	int (*unprepare_transfer_hardware)(struct spi_master *master); // 当没有message传输,驱动将调用这个函数来释放掉相关硬件
};


再看看spi_message:

struct spi_message {
	struct list_head	transfers; // 链表的节点,构成的关系看后边的图

	struct spi_device	*spi;

	unsigned		is_dma_mapped:1; // 是不是使用dma模式

	/* 传输完成后调用的函数相关部分 */
	void			(*complete)(void *context); // 调用的函数首地址
	void			*context; // 调用的函数中传入的参数
	unsigned		actual_length; // 所有传输成功的段中总的字节数
	int			status; // 0,成功,其他的赋值是errno值

	struct list_head	queue; // 链表节点,构成的关系看后边的图
	void			*state; // 被拥有这个message的驱动使用
};

还有 spi_transfer:

struct spi_transfer {
	const void	*tx_buf; // 发送的缓冲区
	void		*rx_buf; // 接收的缓冲区
	unsigned	len; // 缓冲区中可以存放的字节数

	dma_addr_t	tx_dma; // 如果设置了 spi_message.is_dma_mapped 被设置了,这个变量时tx_buf的DMA地址
	dma_addr_t	rx_dma;

	unsigned	cs_change:1; // 如果设置了,则此次传输完成后将会翻转片选
	u8		bits_per_word; // 此次传输一个word含多少位,如果值是0,则会用默认值
	u16		delay_usecs; // 在此次传输完成到改变片选之前的延时,单位ms,之后开始下一次transfer,或者此次spi_message结束
	u32		speed_hz; // 设置一个速度,0会用默认值

	struct list_head transfer_list; // 看后边的图,链表的节点
};

这几个结构再应用中的关系如下图:



3. spidev.c 文件分析:


在文件的开头声明了一个32位的bitmap(bitmap相关知识,跳转到这里)和一个链表的头:

#define SPIDEV_MAJOR			153	/* assigned */
#define N_SPI_MINORS			32	/* ... up to 256 */

static DECLARE_BITMAP(minors, N_SPI_MINORS); /* 声明了一个含32位的bitmap,用于做为spi设备的索引 */
static LIST_HEAD(device_list); /* 声明一个链表的头 */
static DEFINE_MUTEX(device_list_lock); /* 实现对链表原子操作的互斥锁 */

然后,我们从 module_init(spidev_init); 开始

static int __init spidev_init(void)
{
	int status;
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); /* 注册主设备号是 153 的字符设备,同时注册上对字符设备的操作的函数 */
	spidev_class = class_create(THIS_MODULE, "spidev"); /* 创建一个名字是 spidev 的类,为mdev/udev在 /dev 下创建节点做准备 */
	status = spi_register_driver(&spidev_spi_driver);   /* 注册 驱动程序 */
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	spi_unregister_driver(&spidev_spi_driver);
	class_destroy(spidev_class);
	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
module_exit(spidev_exit);

【1】上边的程序中省略了错误检查和处理部分

【2】spi devfs 驱动的主设备号固定是 153


再来看看 spi_driver 结构体

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"spidev",
		.owner =	THIS_MODULE,
	},
	.probe =	spidev_probe,
	.remove =	__devexit_p(spidev_remove),
};

spi_driver 结构体是啥样子,和 platform_driver 驱动有什么相似和不同

struct spi_driver {
	const struct spi_device_id *id_table;
	int (*probe)(struct spi_device *spi);
	int (*remove)(struct spi_device *spi);
	void (*shutdown)(struct spi_device *spi);
	int (*suspend)(struct spi_device *spi, pm_message_t mesg);
	int (*resume)(struct spi_device *spi);
	struct device_driver	driver;
};

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
};


言归正传,在和设备匹配上的时候调用匹配函数 probe 函数:spidev_probe

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

	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); /* 申请并初始化一个 spidev_data 的区域 */

	spidev->spi = spi; /* 将spidev_data结构体中的spi指针指向匹配好的spi设备 */
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);/* 得到互斥锁 */
	minor = find_first_zero_bit(minors, N_SPI_MINORS); /* 在0-31中找到一个没有被使用的作为次设备号 */
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor); /* 赋值spidev中的设备号 */
		dev = device_create(spidev_class, &spi->dev, spidev->devt, /* 创建设备节点,名字是spidevA.B,A是设备结构体中的master.bus_num,B是设备中的chipselect */
				    spidev, "spidev%d.%d",                 /* A是设备结构体中的master.bus_num,B是设备中的chipselect */
				    spi->master->bus_num, spi->chip_select);
		status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors); /* 将使用了0-31中使用了作为次设备号的位置1 */
		list_add(&spidev->device_entry, &device_list); /* 将组装好的spidev_data结构体添加到链表中 */
	}
	mutex_unlock(&device_list_lock); /* 解锁 */

	if (status == 0)
		spi_set_drvdata(spi, spidev); /* 将spi.dev.p指向这个组装好的spi_data结构体,方便整个文件中其他函数的使用 */
	else
		kfree(spidev);

	return status;
}

【1】spidev_data 结构体

struct spidev_data {
	dev_t			devt;         /* 设备号 */
	spinlock_t		spi_lock;     /* 自旋锁 */
	struct spi_device	*spi;         /* 指向spi设备结构体的指针 */
	struct list_head	device_entry; /* 链表的节点入口 */

	struct mutex		buf_lock;     /* 互斥锁 */
	unsigned		users;
	u8			*buffer;
};

删除函数 remove:spidev_remove

static int __devexit spidev_remove(struct spi_device *spi)
{
	struct spidev_data	*spidev = spi_get_drvdata(spi); /* 得到设备结构体 */

	/* make sure ops on existing fds can abort cleanly */
	spin_lock_irq(&spidev->spi_lock); /* 获得自旋锁 */
	spidev->spi = NULL;
	spi_set_drvdata(spi, NULL);
	spin_unlock_irq(&spidev->spi_lock); /* 释放自旋锁 */

	/* prevent new opens */
	mutex_lock(&device_list_lock);
	list_del(&spidev->device_entry); /* 从链表中删除设备节点 */
	device_destroy(spidev_class, spidev->devt); /* 删类 */
	clear_bit(MINOR(spidev->devt), minors); /* 去除bitmap中的次设备号相应的位 */
	if (spidev->users == 0) /* 这里保证驱动程序没有被使用 */
		kfree(spidev);
	mutex_unlock(&device_list_lock);

	return 0;
}

上边是设备节点的创建,接下来,我们看看对设备节点的操作函数:

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	.write =	spidev_write,
	.read =		spidev_read,
	.unlocked_ioctl = spidev_ioctl,
	.compat_ioctl = spidev_compat_ioctl,
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	no_llseek,
};
字符设备打开和退出函数:spidev_open、spidev_release

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

	mutex_lock(&device_list_lock);

	list_for_each_entry(spidev, &device_list, device_entry) {
		if (spidev->devt == inode->i_rdev) { /* 遍历链表,看有没有相同设备号的设备,有的话 status = 0,并将spidev指向相应位置 */
			status = 0;
			break;
		}
	}
	if (status == 0) { /* 设备号有效 */
		if (!spidev->buffer) {
			spidev->buffer = kmalloc(bufsiz, GFP_KERNEL); /* 第一次打开,buffer指向一个预留出一个4096大小的空间 */
			if (!spidev->buffer) {
				dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
				status = -ENOMEM;
			}
		}
		if (status == 0) {
			spidev->users++; /* 用户数目加1 */
			filp->private_data = spidev; /* 将spi设备结构体地址放到filp的private_data中,方便读写的时候引用 */
			nonseekable_open(inode, filp); /* 【1】 */
		}
	} else
		pr_debug("spidev: nothing for minor %d\n", iminor(inode));

	mutex_unlock(&device_list_lock);
	return status;
}

static int spidev_release(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int			status = 0;

	mutex_lock(&device_list_lock);
	spidev = filp->private_data; /* 得到spidev_data结构体 */
	filp->private_data = NULL; /* 清指针 */

	/* last close? */
	spidev->users--; /* 将spidev中的用户数减1 */
	if (!spidev->users) { /* 如果只有一个用户程序访问 */
		int		dofree;

		kfree(spidev->buffer); /* 显式的释放在open的时候分配的内存空间 */
		spidev->buffer = NULL;

		/* ... after we unbound from the underlying device? */
		spin_lock_irq(&spidev->spi_lock);
		dofree = (spidev->spi == NULL); /* 判断此时是不是spi设备没有了 */
		spin_unlock_irq(&spidev->spi_lock);

		if (dofree)
			kfree(spidev); /* 当spi设备此时也不在了,直接释放掉spidev_data */
	}
	mutex_unlock(&device_list_lock);

	return status;
}

【1】nonseekable_open(inode, filp); 展开如下:

int nonseekable_open(struct inode *inode, struct file *filp)
{
	filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
	return 0;
}
/* file is seekable */
#define FMODE_LSEEK		((__force fmode_t)0x4)
/* file can be accessed using pread */
#define FMODE_PREAD		((__force fmode_t)0x8)
/* file can be accessed using pwrite */
#define FMODE_PWRITE		((__force fmode_t)0x10)


再来看看文件操作结构体file_operations中的.write,spidev_write函数:

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) /* 如果写的数据个数大于4096,则返回错误 */
		return -EMSGSIZE;

	spidev = filp->private_data; /* 得到spidev_data结构体数据地址 */

	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->buffer, buf, count); /* 将用户数据拷贝到spidev中的buffer指向的区域 */
	if (missing == 0) { /* 返回0,拷贝成功 */
		status = spidev_sync_write(spidev, count); // 将buffer中的内容发送出去【1】
	} else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}
【1】spidev_sync_write函数实现如下:

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len) /* sync 同步,这个函数是同步写 */
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->buffer, /* 将发送的数据指针指向spidev中的buffer */
			.len		= len,
		};
	struct spi_message	m;

	spi_message_init(&m); /* 清空m空间,初始化m中的transfers链表头 */
	spi_message_add_tail(&t, &m); /* 将t中的transfer_list节点添加到m中的transfer链表的尾部 */
	return spidev_sync(spidev, &m); // 此函数看下边函数实现
}

static inline void spi_message_init(struct spi_message *m)
{
	memset(m, 0, sizeof *m);
	INIT_LIST_HEAD(&m->transfers);
}

static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
	list_add_tail(&t->transfer_list, &m->transfers);
}

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done); /* 声明一个完成量 */
	int status;

	message->complete = spidev_complete; /* 关联上传输完成后的回调函数,回调函数中只做了唤醒一个等待的执行单元这一个操作 */
	message->context = &done; /* 完成量作为 sidev_complete 函数的参数 */

	spin_lock_irq(&spidev->spi_lock);
	if (spidev->spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_async(spidev->spi, message); /* 传输message,通过调用spi_async->__spi_async->master->transfer完成数据的传输,*/
                                                          /*这个函数里边直接调用了__spi_async 函数,如【1】 */
	spin_unlock_irq(&spidev->spi_lock);

	if (status == 0) {
		wait_for_completion(&done); /* 阻塞的等待完成量的完成 */
		status = message->status;
		if (status == 0)
			status = message->actual_length;
	}
	return status; /* 返回实际发送的长度 */
}

【1】__spi_async的实现:

static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;

        /* 做一些保证工作 */

	message->spi = spi;
	message->status = -EINPROGRESS;
	return master->transfer(spi, message); /* 根据spi中的一些参数将message加入到队列中 */
}


#这里是错误的开始

以下是错误的分析,以下是在spidev驱动中本来应该是可以这样的,但是,应该是早期驱动的不完善,分析到最后,发现有几个函数的默认函数是没有提供的,在查看v3.13 版本的内核后发现慢慢的在添加默认函数。

master->transfer 的关联的工作在  spi_register_master(此函数依然在spi.c文件中)函数中:

int spi_register_master(struct spi_master *master)
{
        ...
	/* If we're using a queued driver, start the queue */
	if (master->transfer) // 如果控制器中指定了transfer,打印信息
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else {
		status = spi_master_initialize_queue(master);
		if (status) {
			device_unregister(&master->dev);
			goto done;
		}
	}

        ...
}

接下来我们来分析分析 spi_master_initialize_queue  函数:

static int spi_master_initialize_queue(struct spi_master *master)
{
	int ret;

	master->queued = true; // 使能队列
	master->transfer = spi_queued_transfer; // 关联发生在这里

	ret = spi_init_queue(master); // spi初始化一个队列
	if (ret) {
		goto err_init_queue;
	}
	ret = spi_start_queue(master); // spi开始一个队列的传输
	if (ret) {
		goto err_start_queue;
	}

	return 0;

err_start_queue:
err_init_queue:
	spi_destroy_queue(master); // 遇到错误,在退出前需要销毁队列
	return ret;
}
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
	struct spi_master *master = spi->master;
	unsigned long flags;

	msg->actual_length = 0;
	msg->status = -EINPROGRESS;

	list_add_tail(&msg->queue, &master->queue); // 将msg的链表的入口挂接到master的queue队列的后边
	if (master->running && !master->busy) // master处于运行态,但是不忙的情况
		queue_kthread_work(&master->kworker, &master->pump_messages); // 开始 master 的kthread_work挂到kthread_worder中的work_list下,
                                                                              // 并且处理 kthread_work
	spin_unlock_irqrestore(&master->queue_lock, flags);
	return 0;
}
static int spi_init_queue(struct spi_master *master)
{
	struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; // 这个值是最大内核线程优先级

	INIT_LIST_HEAD(&master->queue); // 初始化master中的queue链表头
	spin_lock_init(&master->queue_lock);

	master->running = false;
	master->busy = false;

	init_kthread_worker(&master->kworker); // 初始化master中的kthread_worker
	master->kworker_task = kthread_run(kthread_worker_fn, // 创建内核线程,执行kthread_worker的work_list下挂载的kthread_work中的func
					   &master->kworker,
					   dev_name(&master->dev));
        ...
	init_kthread_work(&master->pump_messages, spi_pump_messages); // 初始化kthread_work,并将kthread_work中的func指向spi_pump_message
                                                                      // spi_pump_messages 是实际的对硬件的操作
	if (master->rt) { // 如果设置了 bool 行的rt为true,则将会把master中的kworker_task的任务优先级设置为最高 (MAX_RT_PRIO - 1)
		dev_info(&master->dev,
			"will run message pump with realtime priority\n");
		sched_setscheduler(master->kworker_task, SCHED_FIFO,&param);
	}

	return 0;
}
static int spi_start_queue(struct spi_master *master)
{
	unsigned long flags;

	spin_lock_irqsave(&master->queue_lock, flags);

	if (master->running || master->busy) {
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return -EBUSY;
	}

	master->running = true;
	master->cur_msg = NULL;
	spin_unlock_irqrestore(&master->queue_lock, flags);

	queue_kthread_work(&master->kworker, &master->pump_messages); // 将 kthread_work 挂接到 kthread_worder 中的work_list下,此时kthread_worker
                                                                      // 处于就绪态,只要是挂接了 kthread_work,就会执行kthread_work中的函数
	return 0;
}
static int spi_destroy_queue(struct spi_master *master)
{
	int ret;

	ret = spi_stop_queue(master); // 尝试的将master中的running赋值成false,当然要查看是不是正在运行,如果一直运行,超过5s也会将running赋值成false

	if (ret) {
		dev_err(&master->dev, "problem destroying queue\n");
		return ret;
	}

	flush_kthread_worker(&master->kworker); // 等待kthread_worker中work_list下挂载的所有work都执行完
	kthread_stop(master->kworker_task); // 停止内核线程

	return 0;
}
【1】kthread_worker和kthread_work部分请查看  http://blog.csdn.net/qqliyunpeng/article/details/53931350


kthread_work中的函数 spi_pump_messages函数分析:

/*-------------------------------------------------------------------------*/

/**
 * spi_pump_messages - kthread work 中执行spi的message队列的函数
 * @work: 指向包含在master结构体中的kthread_work结构体
 *
 * 这个函数的功能是检查在队列中是不是有spi message,如果有,则调用驱动初始化硬件
 * 并且开始传输每个message
 *
 */
static void spi_pump_messages(struct kthread_work *work)
{
	struct spi_master *master =
		container_of(work, struct spi_master, pump_messages); // 得到包含pump_messages的spi_master结构体
	unsigned long flags;
	bool was_busy = false;
	int ret;

	/* Lock queue and check for queue work */
	spin_lock_irqsave(&master->queue_lock, flags);
	if (list_empty(&master->queue) || !master->running) { // 如果master中的queue下挂着spi_message,并且master没有运行
		if (master->busy) {
			ret = master->unprepare_transfer_hardware(master); // 如果master处于busy状态,则释放硬件
			if (ret) {
				spin_unlock_irqrestore(&master->queue_lock, flags);
				dev_err(&master->dev,
					"failed to unprepare transfer hardware\n");
				return;
			}
		}
		master->busy = false; 
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return;
	}

	if (master->cur_msg) { // 确保没有正在传输的message
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return;
	}
	master->cur_msg =
	    list_entry(master->queue.next, struct spi_message, queue); // 找到存储在master中queue下的第一个message

	list_del_init(&master->cur_msg->queue); // 删除挂接到queue下队列中的第一个message
	if (master->busy)
		was_busy = true; // 表示不是master中queue下的第一个message
	else
		master->busy = true;
	spin_unlock_irqrestore(&master->queue_lock, flags);

	if (!was_busy) { // 如果是master queue下的第一个message
		ret = master->prepare_transfer_hardware(master); // 初始化传输前的硬件
		if (ret) {
			dev_err(&master->dev,
				"failed to prepare transfer hardware\n");
			return;
		}
	}

	ret = master->transfer_one_message(master, master->cur_msg); // 传输master中queue下的第一个message
	if (ret) {
		dev_err(&master->dev,
			"failed to transfer one message from queue\n");
		return;
	}
}


我们再来分析分析master下的跟硬件相关设置的几个函数:.prepare_transfer_hardware函数、transfer_one_message函数、unprepare_transfer_hardware函数:

到这里,发现,驱动中竟然没有这几个默认函数的实现,至此,驱动的分析认为是错误的。但是到了后期的版本,v3.13之后,默认函数添加上了,在这里先不分析了。

#这里是错误的结束

master->transfer 的关联工作在 spi-s3c24xx.c 中的s3c24xx_spi_probe函数中的spi_bitbang_start 中,我们来看看spi_bitbang_start函数:

/**
 * spi_bitbang_start - 开始一个 polled/bitbanging SPI master 驱动
 * @bitbang: driver 句柄
 *
 * 调用者应该已经初始化了结构体的所有部分为0,并且提供了片选的回调函数和I/O循环
 * 如果master已经有一个transfer方法,最后一步应该调用 spi_bitbang_transfer
 *
 * 对于 i/o loops, 提供回调每个word(对于bitbanging或者对于硬件,相当于基本的移位寄存器)
 * 或者是每个spi_transfer(这样能更好的利用硬件如fifos或者DMA).
 *
 * 使用 per-word I/O loops 应该使用 spi_bitbang_setup,spi_bitbang_cleanup 和
 * spi_bitbang_setup_transfer去处理spi master 的方法.  这些方法是默认的如果bitbang->txrx_bufs
 * 没有指定
 *
 * 这个函数注册spi_master, 这个函数将会处理请求在一个专有任务中,在大多数情况下
 * 保持 IRQ 的非阻塞状态.如果想停止执行请求,调用call spi_bitbang_stop().
 */
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
	int	status;

	INIT_WORK(&bitbang->work, bitbang_work);
	spin_lock_init(&bitbang->lock);
	INIT_LIST_HEAD(&bitbang->queue);

	if (!bitbang->master->mode_bits) // 在s3c_24xx_spi_probe 中有赋值,不为空
		bitbang->master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;

	if (!bitbang->master->transfer) // 在这里,master的transfer还没指定,是NULL,所以执行下边语句
		bitbang->master->transfer = spi_bitbang_transfer; // 这里就是 master->transfer的赋值的地方了
	if (!bitbang->txrx_bufs) { // 在s3c24xx_spi_probe 中有赋值,不为空
                    ...
	} else if (!bitbang->master->setup) // 不为空
		return -EINVAL;

	/* this task is the only thing to touch the SPI bits */
	bitbang->busy = 0;
	bitbang->workqueue = create_singlethread_workqueue( // 创建一个workqueue_struct空间,并且返回它的地址
			dev_name(bitbang->master->dev.parent));

	/* driver may get busy before register() returns, especially
	 * if someone registered boardinfo for devices
	 */
	status = spi_register_master(bitbang->master);

	return status;
}
【1】此部分函数中移除了错误处理的部分,只保留了主要的过程和步骤

我们来看看此时的spi_register_master函数:

int spi_register_master(struct spi_master *master)
{
	struct device		*dev = master->dev.parent;
	struct boardinfo	*bi;
	int			status = -ENODEV;

	/* 注册设备(device),之后用户空间将会能够看到它.
	 */
	status = device_add(&master->dev);

	if (master->transfer) // 在这里,我们上边刚刚添加了关联,因此,此处不是NULL,打印信息
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else { // 此处不执行
		status = spi_master_initialize_queue(master);
                ...
	}

	list_add_tail(&master->list, &spi_master_list); // master 挂接到 spi_master_list 链表头下
	list_for_each_entry(bi, &board_list, list)
		spi_match_master_to_boardinfo(master, &bi->board_info); // 比较控制器和设备,如果有了相同的bus_num,则创建一个新设备(device)
	mutex_unlock(&board_lock);

	/* 从设备树上注册设备,我们这里没有采用设备树,因此,此函数是空,进去直接返回,,, */
	of_register_spi_devices(master);
done:
	return status;
}


还有一个问题没有解决的是,在什么时候执行了s3c24xx_spi_probe函数呢?是在platform的设备和驱动相匹配了之后执行的:

platfor的设备在下边例子中改动的arch/arm/mach-s3c24xx/mach-mini244o.c 文件中的


platform的驱动在 spi-s3c24xx.c 中mini2440_devices[] 这个platform_device 结构的数组中的&s3c_device_spi1 这句,这个结构是在arch/arm/plat-samsung/devs.c 文件中已经定义好的。另外要说明的一点是在 arch/arm/mach-s3c24xx/mach-mini2440.c 这个文件中还将 s3c_device_spi1 进行了扩展,将struct s3c2410_spi_info 结构类型的数据放到了s3c_device_spi1.dev.platform_data中(此处的意思是将地址给了它):

static struct resource s3c_spi1_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32), // 第一个参数是spi寄存器的基地址,第二个参数是地址所占用的位数
	[1] = DEFINE_RES_IRQ(IRQ_SPI1), // spi1对应的中断
};

struct platform_device s3c_device_spi1 = {
	.name		= "s3c2410-spi", // 跟 spi-s3c24xx.c 中进行匹配的关键
	.id		= 1,
	.num_resources	= ARRAY_SIZE(s3c_spi1_resource),
	.resource	= s3c_spi1_resource,
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};

接下来我们就来具体的分析分析.probe函数:

static int __devinit s3c24xx_spi_probe(struct platform_device *pdev)
{
	struct s3c2410_spi_info *pdata;
	struct s3c24xx_spi *hw;
	struct spi_master *master;
	struct resource *res;
	int err = 0;

	master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); // 分配了一个master的空间

	hw = spi_master_get_devdata(master); // 相当于hw = master->dev->p->driver_data,对hw的操作实际上就是对master->dev->p->driver_data的操作
	memset(hw, 0, sizeof(struct s3c24xx_spi)); // 将从driver_data首地址开始之后分配s3c24xx_spi结构体大小的空间

	hw->master = spi_master_get(master); // 相当于 hw->master = master
	hw->pdata = pdata = pdev->dev.platform_data; // 得到从平台文件中来的platform_device的私有数据
	hw->dev = &pdev->dev;

	platform_set_drvdata(pdev, hw); // pdev->dev->p->driver_data = hw 
	init_completion(&hw->done);

	/* initialise fiq handler */
	s3c24xx_spi_initfiq(hw); // 初始化了fiq

	/* setup the master state. */

	/* the spi->mode bits understood by this driver: */
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; // 模式选择,片选高有效

	master->num_chipselect = hw->pdata->num_cs;
	master->bus_num = pdata->bus_num;

	/* setup the state for the bitbang driver */

	hw->bitbang.master         = hw->master;
	hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
	hw->bitbang.chipselect     = s3c24xx_spi_chipsel;
	hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;

	hw->master->setup  = s3c24xx_spi_setup;
	hw->master->cleanup = s3c24xx_spi_cleanup;

	dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);

	/* find and map our resources */

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	hw->ioarea = request_mem_region(res->start, resource_size(res),
					pdev->name);

	hw->regs = ioremap(res->start, resource_size(res));

	hw->irq = platform_get_irq(pdev, 0);

	err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); //申请中断,发生中断进s3c24xx_spi_irq,此函数中处理接收的数据

	hw->clk = clk_get(&pdev->dev, "spi");

	/* setup any gpio we can */
	if (!pdata->set_cs) { // 我们的mach-mini2440文件中没有提供这个函数
		if (pdata->pin_cs < 0) {
			dev_err(&pdev->dev, "No chipselect pin\n");
			goto err_register;
		}

		err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev)); // 验证一下cs的gpio的有效性

		hw->set_cs = s3c24xx_spi_gpiocs; // 操作片选的函数关联上
		gpio_direction_output(pdata->pin_cs, 1); // 1表示通过判断gpio_desc中的flag来设置输入输出
	} else
		hw->set_cs = pdata->set_cs; // 如果提供了,用用户的

	s3c24xx_spi_initialsetup(hw); // 设置spi控制器的初始状态

	/* register our spi controller */
	err = spi_bitbang_start(&hw->bitbang); // 开始以个spi_master 驱动,上边有函数分析

	return 0;
}

我们来看看建立传输阶段:

/**
 * spi_bitbang_transfer - 默认传给发送队列
 */
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
	struct spi_bitbang	*bitbang;
	unsigned long		flags;
	int			status = 0;

	m->actual_length = 0;
	m->status = -EINPROGRESS;

	bitbang = spi_master_get_devdata(spi->master); // 得到数据

	if (!spi->max_speed_hz)
		status = -ENETDOWN;
	else {
		list_add_tail(&m->queue, &bitbang->queue);
		queue_work(bitbang->workqueue, &bitbang->work); // 提交给工作队列,开始实际的传输工作
	}

	return status;
}

实际的传输是s3c24xx_spi_txrx:

static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
{
	struct s3c24xx_spi *hw = to_hw(spi);

	hw->tx = t->tx_buf;
	hw->rx = t->rx_buf;
	hw->len = t->len;
	hw->count = 0;

	init_completion(&hw->done);

	hw->fiq_inuse = 0;
	if (s3c24xx_spi_usefiq(hw) && t->len >= 3)
		s3c24xx_spi_tryfiq(hw);

	/* send the first byte */
	writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); // 向发送寄存器中写入发送数组中的第一个( hw->tx ? hw->tx[0] : 0;)
                                                             // 我们只管开始第一个,其他的是有工作队列会自动的发送其他的
	wait_for_completion(&hw->done);
	return hw->count;
}

至此,我们先分析到这里,虽然分析完了,可实际上还是有部分不是分析的很清楚,但是感觉到这里,对于一般的spi应用来说是足够了,另外考虑到对spi的分析用的时间比较长,暂时就先到这里。

对spi整体分层是一个linux中设计的精髓,但是,自己还是没能整理出一张好的分层图,这就留到以后的学习和实践中去吧。

4. 程序验证:


对spidev的使用在jz2440开发板上的使用需要先添加平台设备,经过看原理图,发现,控制器0上的引脚并没有引出来。

diff --git a/arch/arm/mach-s3c24xx/mach-mini2440.c b/arch/arm/mach-s3c24xx/mach-mini2440.c
index fa4dc4d..8b16989 100644
--- a/arch/arm/mach-s3c24xx/mach-mini2440.c
+++ b/arch/arm/mach-s3c24xx/mach-mini2440.c
@@ -62,6 +62,9 @@
 
 #include "common.h"
 
+#include <linux/spi/spi.h>  // 在头文件处添加两个头文件
+#include <linux/spi/s3c24xx.h>
+
 #define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)
 
 static struct map_desc mini2440_iodesc[] __initdata = {
@@ -505,10 +508,12 @@ static struct platform_device mini2440_audio = {
 /*
  * I2C devices
  */
+#if 0 // 此部分是为了消除警告
 static struct at24_platform_data at24c08 = {
        .byte_len       = SZ_8K / 8,
        .page_size      = 16,
 };
+#endif
 
 static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
 #if 0
@@ -519,6 +524,44 @@ static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
 #endif
 };
 
+/* // 在i2c下边添加一下的spi部分
+ * spi devices
+ */
+#if 0
+static struct spi_board_info s3c2410_spi0_board[]={
+    [0] = {
+        .modalias = "spidev",
+        .bus_num = 0, // spi controller0
+        .chip_select = 0, // 在控制器下的第一个设备,这个选项必须是从0开始依次递增
+        /*.irq = IRQ_EINT9,*/ // 接收数据时用irq通知
+        .max_speed_hz = 500 * 1000,
+    },
+};
+
+static struct s3c2410_spi_info s3c2410_spi0_platdata={
+    .pin_cs  = S3C2410_GPG(2), // 片选引脚
+    .num_cs  = 1, // 几个slave设备
+    .bus_num = 1, // 哪个控制器,从0开始,此处可能有问题,未验证
+};
+#endif
+
+static struct spi_board_info s3c2410_spi1_board[] =
+{
+    [0] = {
+        .modalias = "spidev",
+        .bus_num = 1, // spi controller1
+        .chip_select = 0,
+        /*.irq = IRQ_EINT2,*/ // 此处应该是EINT11(GPG3),此处没有验证
+        .max_speed_hz = 500 * 1000,
+    }
+};
+
+static struct s3c2410_spi_info s3c2410_spi1_platdata = {
+        .pin_cs = S3C2410_GPG(3), // GPG3引脚做片选引脚
+        .num_cs = 1,
+        .bus_num = 1, // 控制器1
+};
+
 static struct platform_device uda1340_codec = {
                .name = "uda134x-codec",
                .id = -1,
@@ -528,6 +571,7 @@ static struct platform_device *mini2440_devices[] __initdata = {
        &s3c_device_ohci,
        &s3c_device_wdt,
        &s3c_device_i2c0,
+    &s3c_device_spi1, // 此处看下边解释 【1】
        &s3c_device_rtc,
        &s3c_device_usbgadget,
        &mini2440_device_eth,
@@ -693,10 +737,13 @@ static void __init mini2440_init(void)
        s3c24xx_mci_set_platdata(&mini2440_mmc_cfg);
        s3c_nand_set_platdata(&mini2440_nand_info);
        s3c_i2c0_set_platdata(NULL);
+    s3c_device_spi1.dev.platform_data = &s3c2410_spi1_platdata;
 
        i2c_register_board_info(0, mini2440_i2c_devs,
                                ARRAY_SIZE(mini2440_i2c_devs));
 
+    spi_register_board_info(s3c2410_spi1_board,ARRAY_SIZE(s3c2410_spi1_board));
+
        platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
 
        if (features.count)     /* the optional features */
【1】 s3c_device_spi1 结构:

/* SPI */

#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_spi0_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32), // (开始地址,大小)
	[1] = DEFINE_RES_IRQ(IRQ_SPI0), // spi的中断0
};

struct platform_device s3c_device_spi0 = {
	.name		= "s3c2410-spi",
	.id		= 0,
	.num_resources	= ARRAY_SIZE(s3c_spi0_resource), // 资源个数
	.resource	= s3c_spi0_resource, // 资源首地址
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};

static struct resource s3c_spi1_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32),
	[1] = DEFINE_RES_IRQ(IRQ_SPI1),
};

struct platform_device s3c_device_spi1 = {
	.name		= "s3c2410-spi",
	.id		= 1,
	.num_resources	= ARRAY_SIZE(s3c_spi1_resource),
	.resource	= s3c_spi1_resource,
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};
#endif /* CONFIG_PLAT_S3C24XX */

至此,平台文件算是改动好了,重新编译内核:

make uImage

将生成的uImage拷贝到tftp的下载目录下:

cp arch/arm/boot/uImage /tftpboot/

重新搭建环境,启动开发板,我们就能在/dev下看到spidev1.0 节点了。


测试程序我们选用内核自带的测试程序:Documentation/spi/spidev_test.c

arm-none-linux-gnueabi-gcc spidev_test.c -o spidev_test -march=armv4t

拷贝生成的spidev_test 到nfs共享目录下的根文件下:

cp spidev_test /source/fs_mini/

由于是半双工,我们可以将 MOSI 和 MISO 用一根线短路。


启动开发板,在开发板上可以看到如下图中内容:


  • 11
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 C 语言中写一个 SPI 驱动程序,需要考虑以下几个步骤: 1. 包含必要的头文件,如 stdint.h 和 stddef.h。 2. 定义一些宏,用于操作系统相关的函数调用。例如,在 Linux 系统中,可以使用宏 _IOW() 和 _IOR() 定义输入/输出控制命令。 3. 定义一些结构体,用于存储设备信息和传输数据。例如,可以定义 spi_transfer 结构体来表示一次数据传输的信息。 4. 实现一些函数,用于打开、关闭、读取和写入设备。例如,可以实现 spi_write_byte() 函数来向设备写入一个字节的数据。 5. 在主函数中调用这些函数,实现对设备的控制和数据传输。 下面是一个简单的示例,展示了如何写一个简单的 C 语言 SPI 驱动程序: ``` #include <stdint.h> #include <stddef.h> #define SPI_IOC_MAGIC 'k' #define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 1, uint32_t) #define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 2, uint32_t) struct spi_ioc_transfer { uint64_t tx_buf; uint64_t rx_buf; uint32_t len; uint32_t speed_hz; uint16_t delay_usecs; uint8_t bits_per_word; uint8_t cs_change; uint8_t tx_nbits; uint8 ### 回答2: SPI(Serial Peripheral Interface)是一种串行外设接口协议,用于在微控制器、单片机和外部设备之间传输数据。下面是一个用C语言编写的简单SPI驱动的示例: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <linux/spi/spidev.h> #define SPI_DEVICE "/dev/spidev0.0" int main(void) { int spi_fd; unsigned char tx_buffer[1]; unsigned char rx_buffer[1]; // 打开SPI设备文件 spi_fd = open(SPI_DEVICE, O_RDWR); if (spi_fd < 0) { perror("Failed to open SPI device"); return 1; } // 配置SPI设备 int mode = SPI_MODE_0; int bits_per_word = 8; int speed = 1000000; if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0 || ioctl(spi_fd, SPI_IOC_RD_MODE, &mode) < 0 || ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0 || ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &bits_per_word) < 0 || ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0 || ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) { perror("Failed to configure SPI device"); close(spi_fd); return 1; } // 进行SPI通信 tx_buffer[0] = 0x55; // 发送的数据 struct spi_ioc_transfer spi; spi.tx_buf = (unsigned long)tx_buffer; spi.rx_buf = (unsigned long)rx_buffer; spi.len = 1; spi.delay_usecs = 0; spi.speed_hz = speed; spi.bits_per_word = bits_per_word; if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &spi) < 0) { perror("Failed to transfer SPI data"); close(spi_fd); return 1; } printf("Received data: 0x%x\n", rx_buffer[0]); // 关闭SPI设备 close(spi_fd); return 0; } ``` 上述代码首先打开SPI设备文件`/dev/spidev0.0`,然后通过ioctl函数对SPI设备进行配置,包括模式、每个数据位数和传输速度等。接下来,将要发送的数据存储在`tx_buffer`中,并利用`spi_ioc_transfer`结构进行SPI的传输。最后,通过关闭SPI设备文件来结束SPI驱动程序。 ### 回答3: SPI(Serial Peripheral Interface)是一种同步的串行通信协议,常用于外设与微控制器之间的通信。在C语言中,我们可以编写一个简单的SPI驱动程序如下: 首先,需要引入相关的头文件和定义必要的宏变量: ```c #include <stdio.h> #include <stdint.h> #include <wiringPi.h> #include <wiringPiSPI.h> #define SPI_CHANNEL 0 // SPI通道 #define SPI_SPEED 1000000 // SPI速度 ``` 接下来,编写函数来初始化SPI驱动: ```c void spi_init() { wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED); } ``` 然后,编写函数来发送数据到外设: ```c void spi_send(uint8_t* data, uint8_t length) { wiringPiSPIDataRW(SPI_CHANNEL, data, length); } ``` 最后,编写函数来接收数据从外设: ```c void spi_receive(uint8_t* data, uint8_t length) { wiringPiSPIDataRW(SPI_CHANNEL, NULL, length); // 发送空数据以激活SPI通信 memcpy(data, wiringPiSPIGetDataRW(SPI_CHANNEL), length); } ``` 通过以上的函数,我们可以初始化SPI驱动并发送和接收数据。以下是一个简单的示例代码: ```c int main() { uint8_t sendData[4] = {0x01, 0x02, 0x03, 0x04}; uint8_t recvData[4] = {0}; spi_init(); spi_send(sendData, sizeof(sendData)); spi_receive(recvData, sizeof(recvData)); for (int i = 0; i < sizeof(recvData); i++) { printf("Received Data: 0x%02X\n", recvData[i]); } return 0; } ``` 以上就是一个简单的C语言SPI驱动程序。当然,实际应用中可能需要更复杂的处理和配置,但上述代码提供了一个基本的框架供参考。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值