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

相关推荐
LunarCod12 分钟前
Ubuntu使用Docker搭建SonarQube企业版(含破解方法)
linux·运维·服务器·ubuntu·docker·开源·sonarqube
betazhou23 分钟前
基于Linux环境实现Oracle goldengate远程抽取MySQL同步数据到MySQL
linux·数据库·mysql·oracle·ogg
什么半岛铁盒29 分钟前
Linux信号的保存
linux·运维·网络
百锦再36 分钟前
大数据技术的主要方向及其应用详解
大数据·linux·网络·python·django·pygame
2301_803554521 小时前
vim,gcc/g++,makefile,cmake
linux·编辑器·vim
惜.己1 小时前
Linux常用命令(十四)
linux·运维·服务器
仰泳之鹅1 小时前
【51单片机中断】
单片机·嵌入式硬件·51单片机
happygrilclh2 小时前
STM32 定时器主从模式配置解析
stm32·单片机·嵌入式硬件
linkingvision2 小时前
H5S 视频监控AWS S3 对象存储
linux·运维·aws·视频监控s3对象存储
belldeep3 小时前
WSL 安装 Debian 12 后,Linux 如何安装 vim ?
linux·debian·vim