目录
前言
一、 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设备驱动:
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设备上进行同步的数据传输。它的函数原型通常如下:
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设备与驱动程序关联起来。它的函数原型通常如下:
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设备的读写操作。它的定义如下:
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函数进行发送。
四、源码
驱动
#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");
应用
#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