Linux驱动入门实验班——DAC模块驱动(附百问网视频链接)

目录

前言

[一、 SPI数据结构](#一、 SPI数据结构)

1.SPI设备驱动

2.SPI设备数据结构

[二 、函数接口](#二 、函数接口)

1.spi_sync_transfer

2.spi_register_driver

三、DAC

1.数据格式

2.数据结构

四、源码

驱动

应用

课程链接


前言

在这里主要记录学习韦东山老师Linux驱动人入门实验班的笔记,韦东山老师的驱动课程讲的非常好,想要学习驱动的小伙伴可以去b站学习他的课程。

一、 SPI数据结构

1.SPI设备驱动

Linux中使用spi_driver结构体描述SPI设备驱动:

cpp 复制代码
struct spi_driver {
    const char *name;  // 设备名称
    struct spi_device *spi;  // 对应的SPI设备
    int (*probe)(struct spi_device *spi);  // 设备探测函数
    int (*remove)(struct spi_device *spi);  // 设备移除函数
    int (*suspend)(struct spi_device *spi, pm_message_t state);  // 设备挂起函数
    int (*resume)(struct spi_device *spi);  // 设备恢复函数
    struct device_driver driver;  // 设备驱动结构体
};

2.SPI设备数据结构

Linux中使用spi_device结构体描述SPI设备,里面记录有设备的片选引脚、频率、挂在哪个SPI控制器下面:

spi_device是一个用于描述SPI设备的结构体。它包含了一些重要的字段,用于表示SPI设备的一些属性和配置信息。以下是spi_device结构体的一些常见字段:

  • bus:表示SPI设备所连接的SPI总线编号。
  • chip_select:表示SPI设备的片选信号编号,用于选择要与之通信的设备。
  • mode:表示SPI设备的工作模式,包括时钟极性、时钟相位等。
  • bits_per_word:表示每个传输字节的位数,通常为8位。
  • max_speed_hz:表示SPI设备的最大传输速度。
  • chip_select_active:表示SPI设备的片选信号的激活电平。
  • delay_between_messages_us:表示两个消息之间的延迟时间。

二 、函数接口

1.spi_sync_transfer

spi_sync_transfer是SPI接口的同步传输函数,用于在SPI设备上进行同步的数据传输。它的函数原型通常如下:

cpp 复制代码
int spi_sync_transfer(struct spi_device *spi, 
            struct spi_transfer *transfers, int num_transfers);

该函数接收三个参数:

  • struct spi_device *spi:指向表示SPI设备的结构体的指针。该结构体包含有关SPI设备的信息,例如片选引脚、模式、时钟频率等。
  • struct spi_transfer *transfers:指向包含传输数据的spi_transfer结构体的数组的指针。每个spi_transfer结构体描述了一次SPI传输的参数,例如发送缓冲区、接收缓冲区、传输长度等。
  • int num_transfers:要执行的SPI传输的次数。

该函数的返回值为传输的结果,通常为负数表示出错,0表示成功完成传输。

在调用spi_sync_transfer函数时,它会顺序执行传输数组中的每个传输,完成后返回传输结果。如果需要进行多次传输,可以在transfers数组中添加多个spi_transfer结构体,并将num_transfers设置为传输的次数。

2.spi_register_driver

spi_register_driver函数用于在Linux内核中注册SPI驱动程序,以便将SPI设备与驱动程序关联起来。它的函数原型通常如下:

cpp 复制代码
int spi_register_driver(struct spi_driver *driver);

该函数接收一个参数:

  • struct spi_driver *driver:指向表示SPI驱动程序的结构体的指针。该结构体包含有关驱动程序的信息和回调函数,例如设备匹配表、probe函数、remove函数等。

该函数的返回值为注册结果,通常为负数表示出错,0表示成功注册。

然后spi_unregister_driver就是反注册了。

三、DAC

1.数据格式

由于 TLC5615 是 10 位 DAC,它允许主控每次发送 12 位或者 16 位的数据, 12 位和 16 位的发送数据格式要求如下图所示。

2.数据结构

spi_transfer结构体是用于表示SPI传输的数据结构,在Linux内核中使用以进行SPI设备的读写操作。它的定义如下:

cpp 复制代码
struct spi_transfer {
    const void *tx_buf;
    void *rx_buf;
    unsigned int len;
    struct spi_message *spi;
    void *context;
    u64 timestamp;
    u32 speed_hz;
    u16 delay_usecs;
    u8 bits_per_word;
    u8 cs_change;
    u8 tx_nbits;
    u8 rx_nbits;
};

该结构体的成员说明如下:

  • const void *tx_buf:指向要发送的数据缓冲区的指针。
  • void *rx_buf:指向接收数据的缓冲区的指针。
  • unsigned int len:要传输的数据长度(以字节为单位)。
  • struct spi_message *spi:指向要执行传输的SPI消息的指针。
  • void *context:可以用于传递上下文信息的指针。
  • u64 timestamp:传输的时间戳。
  • u32 speed_hz:传输速度(以Hz为单位)。
  • u16 delay_usecs:传输之间的延迟时间(以微秒为单位)。
  • u8 bits_per_word:每个字的位数。
  • u8 cs_change:传输结束后是否保持片选(CS)信号的状态。
  • u8 tx_nbits:传输时发送的位数。
  • u8 rx_nbits:传输时接收的位数。

使用前最好先将该结构体清零,将构造好的结构体使用spi_sync_transfer函数进行发送。

四、源码

驱动

cpp 复制代码
#include "asm/uaccess.h"
#include "linux/delay.h"

#include <linux/module.h>
#include <linux/poll.h>
#include "linux/i2c.h"
#include <linux/spi/spi.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

static int major;
static struct class *dac_class;
static struct spi_device *g_spi;

static ssize_t dac_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned short val;
	int err;
	unsigned char kernel_buf[2];

	struct spi_transfer t;
	memset(&t, 0, sizeof(t));

	if (size != 2)
	{
		return -EINVAL;
	}

	err = copy_from_user(&val, buf, 2);
	val <<= 2;
	val &= 0x0fff;

	kernel_buf[0] = val >> 8;
	kernel_buf[1] = val;

	t.tx_buf = kernel_buf;
	t.len = 2;

	err = spi_sync_transfer(g_spi, &t, 1);

	return err;
}

static struct file_operations dac_fops = {
	.owner = THIS_MODULE,
	.write = dac_drv_write,
};



static struct of_device_id spi_dt_match[] = {
	{.compatible = "spidev,dac"},
};

static int dac_drv_probe(struct spi_device *spi)
{
	// struct device_node *np = client->dev.of_node;

	/* 记录spi_device */
	g_spi = spi;

	/* 注册字符设备 */
	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_spi", &dac_fops);  /* /dev/gpio_desc */

	dac_class = class_create(THIS_MODULE, "100ask_spi_class");
	if (IS_ERR(dac_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_spi");
		return PTR_ERR(dac_class);
	}

	device_create(dac_class, NULL, MKDEV(major, 0), NULL, "myspi"); /* /dev/myspi */
	
	return 0;
}

static int dac_drv_remove(struct spi_device *spi)
{
	device_destroy(dac_class, MKDEV(major, 0));
	class_destroy(dac_class);
	unregister_chrdev(major, "dac_drv");

	return 0;
}

static struct spi_driver spi_dac_drive = {
	.driver = {
		.name = "my_spi_dac",
		.owner = THIS_MODULE,
		.of_match_table = spi_dt_match,
	},
	.probe = dac_drv_probe,
	.remove = dac_drv_remove,
};

static int __init dac_init(void)
{
	return spi_register_driver(&spi_dac_drive);
}

static void __exit dac_exit(void)
{
	spi_unregister_driver(&spi_dac_drive);
}

module_init(dac_init);
module_exit(dac_exit);

MODULE_LICENSE("GPL");

应用

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

/*
 * dac_test /dev/mydac <val>
 */

int main(int argc, char **argv)
{
	int fd;
	int buf[2];
	unsigned short dac_val = 0;

	if (argc != 3)
	{
		printf("Usage: %s <dev> <val>\n", argv[0]);
		return -1;
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf(" can not open %s\n", argv[1]);
		return -1;
	}

	dac_val = strtoul(argv[2], NULL, 0);

//	while (1)
	{
		write(fd, &dac_val, 2);
		dac_val += 50;
	}
	
	return 0;
}

课程链接

百问网韦老师的liunx驱动入门实验班https://video.100ask.net/p/t_pc/course_pc_detail/video/v_639436ffe4b0fc5d1213db21?product_id=p_634cbce4e4b00a4f37500252&content_app_id=&type=6

相关推荐
意疏34 分钟前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu41 分钟前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
lantiandianzi2 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网2 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式2 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
我的K84092 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900432 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo2 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器
Linux运维日记3 小时前
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
linux·docker·云原生·容器·kubernetes
我是唐青枫3 小时前
Linux dnf 包管理工具使用教程
linux·运维·服务器