文章目录
- 一、I2C驱动框架
- (1)I2C驱动框架介绍
- (2)I2C总线驱动介绍
- 【1】i2c_adapter结构体
- 【2】i2c_algorithm结构体
- 【3】I2C总线驱动工作介绍
- (3)I2C设备驱动介绍
- 【1】i2c_client结构体
- 【2】i2c_driver结构体
- (4)I2C 核心介绍
- 二、sht20驱动开发流程
- (1)sht20相关命令
- (2)驱动程序结构
- 三、sht20驱动开发
- (1)硬件连接
- (2)修改编译设备树
- (3)编写驱动代码
- (4)执行结果
- 四、重难点分析
- (1)Linux下DEVICE_ATTR介绍
- 【1】DEVICE_ATTR宏定义
- 【2】代码实现
- 【3】测试讲解
- (2)container_of介绍
- 【1】作用介绍
- 【2】举个栗子
源码是学习上一届学长的,想要参考的可以去拜访一下gitee:源码链接
对于I2C不了解的可以参考这篇文章,里面有介绍I2C的相关知识以及时序:I2C协议介绍以及HAL库实现I2C对SHT30温湿度采样
sht20获取数据具体流程(软复位、数据处理公式)可以参考这篇文章:IGKBoard(imx6ull)-I2C接口编程之SHT20温湿度采样
具体tftp服务器搭建不懂的可以参考这篇文章:wpa_supplicant无线网络配置imx6ull以及搭建tftp服务器
uboot使用tftp从网络启动设备树和zImage的可以参考这篇文章:Linux嵌入式uboot使用tftp网络启动加载zImage、设备树
一、I2C驱动框架
(1)I2C驱动框架介绍
在 Linux 内核中 I2C 的体系结构分为 3 个部分:
- I2C 总线驱动: I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现, 适配器可由CPU 控制, 甚至可以直接集成在 CPU 内部。I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
- I2C 设备驱动: I2C 设备驱动是对 I2C 硬件体系结构中设备端的实现, 设备一般挂接在受 CPU 控制的 I2C 适配器上, 通过 I2C 适配器与 CPU 交换数据。
- I2C 核心: I2C 核心提供了 I2C 总线驱动和设备驱动的注册、 注销方法
在 Linux 系统中则采用了总线、设备驱动模型。我们之前讲解的平台设备也是采用了这种模型,只不过平台总线是一个虚拟的总线。
我们知道一个i2c(例如i2c1)上可以挂在多个i2c设备,例如sht20、i2c接口的OLED显示屏、摄像头(摄像头通过i2c接口发送控制信息)等等, 这些设备共用一个i2c,这个i2c的驱动我们称为i2c总线驱动。而对应具体的设备,例如sht20的驱动就是i2c设备驱动。 这样我们要使用sht20就需要拥有“两个驱动”一个是i2c总线驱动和sht20设备驱动。i
- 2c总线驱动由芯片厂商提供(驱动复杂,官方提供了经过测试的驱动,我们直接用)。
- sht20设备驱动可以从sht20芯片厂家那里获得,也可以我们手动编写。
i2c 总线包括 i2c 设备 (i2c_client) 和 i2c 驱动 (i2c_driver), 当我们向 linux 中注册设备或驱动的时候,按照 i2c 总线匹配规则进行配对,配对成功,则可以通过 i2c_driver 中.prob 函数创建具体的设备驱动。
在现代 linux 中,i2c 设备不再需要手动创建,而是使用设备树机制引入,设备树节点是与 paltform 总线相配合使用的。
设备驱动创建成功,我们还需要实现设备的文件操作接口 (file_operations),file_operations 中会使用到内核中 i2c 核心函数 (i2c 系统已经实现的函数,专门开放给驱动工程师使用)。使用这些函数会涉及到 i2c 适配器,也就是 i2c 控制器。由于 ic2 控制器有不同的配置,所有 linux 将每一个 i2c控制器抽象成 i2c 适配器对象。这个对象中存在一个很重要的成员变量——Algorithm,Algorithm中存在一系列函数指针,这些函数指针指向真正硬件操作代码。下面会介绍到。
(2)I2C总线驱动介绍
I2C 总线和 platform 总线类似, 区别在于platform 总线是虚拟的一条总线, 而 I2C 总线是实际
存在的。 对于使用 I2C 通信的设备, 在驱动中直接使用 I2C 总结即可。 I2C 总线驱动的重点是 I2C 适配器驱动, 主要涉及到两个结构体: i2c_adapter 和 i2c_algorithm。
【1】i2c_adapter结构体
i2c_ 适配器对应一个 i2c 控制器,是用于标识物理 i2c 总线以及访问它所需的访问算法的结构。
在 Linux 内核中用 i2c_adapter 结构体来表示 I2C 适配器。 i2c_adapter 结构体定义在 include/linux/i2c.h 文件中。
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* 总线访问算法 */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
【2】i2c_algorithm结构体
structi2c_algorithm(/ˈælɡərɪðəm/) 结构体用于指定访问总线(i2c)的算法,在这里就是用于指定外部访问 i2c 总线的接口,这个“接口”体现到代码就是一些接口函数。从下面代码不难看出 i2c_algorithm 结构体实际提供了一些函数指针,这些函数就是外部访问 i2c 总线的接口,更直白的说,i2c 设备sht20、i2c 接口的 oled 屏等等就是通过这些函数接口使用i2c 总线实现收、发数据的。
i2c_algorithm 是 I2C 适配器与 IIC 设备进行通信的方法。i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中。
struct i2c_algorithm
{
......
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
......
};
【3】I2C总线驱动工作介绍
I2C 总线驱动(I2C 适配器驱动)的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。最后通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册i2c_adapter。
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,所以大多数只要专注于 I2C 设备驱动即可。
(3)I2C设备驱动介绍
在I2C设备驱动中主要有两个重要的结构体: i2c_client 和 i2c_driver。 i2c_client 是描述设备信息的,i2c_driver 描述驱动内容。
【1】i2c_client结构体
i2c_client表示 i2c 从设备,当驱动和设备匹配成功后,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client,这个ic_client存储着这个设备的所有信息,如芯片地址。
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 芯片地址, 7 位,存在低 7 位*/
......
char name[I2C_NAME_SIZE]; /* 名字 */
struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
struct device dev; /* 设备结构体 */
int irq; /* 中断 */
struct list_head detected;
......
};
【2】i2c_driver结构体
i2c 设备驱动程序,当 I2C 设备和驱动匹配成功以后 probe 函数就会执行device_driver 驱动结构体:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should
* avoid using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
/*i2c 设备和 i2c 驱动匹配后,回调该函数指针。*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the
* protocol.For the SMBus alert protocol, there is a single bit
* of data passed as the alert response's low bit ("eventflag"). */
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific
* functions with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd,void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
(4)I2C 核心介绍
在 I2C 核心层完成的是I2C设备和I2C驱动的匹配过程。设备和驱动的匹配过程是在 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
match 成员变量是 I2C 总线上设备和驱动匹配函数,即i2c_device_match函数。
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C设备和驱动匹配。
二、sht20驱动开发流程
(1)sht20相关命令
宏定义如下:
#define DEV_NAME "sht20" //设备名
#define SHT20_SOFERESET 0xFE // 软复位
#define SHT20_TEMPERATURE_NO_HOLD_CMD 0xF3 // 无主机模式触发温度测量
#define SHT20_HUMIDITY_NO_HOLD_CMD 0xF5 // 无主机模式触发湿度测量
#define SHT20_TEMPERATURE_HOLD_CMD 0xE3 // 主机模式触发温度测量
#define SHT20_HUMIDITY_HOLD_CMD 0xE5 // 主机模式触发湿度测量
(2)驱动程序结构
/*sht20 打开设备 */
static int sht20_open(struct inode *inode, struct file *filp)
{
return 0;
}
/*从设备读取文件*/
static ssize_t sht20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
return sizeof(data);
}
/*关闭设备*/
static int sht20_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*设备操作函数*/
static struct file_operations sht20_fops = {
.owner = THIS_MODULE,
.open = sht20_open,
.read = sht20_read,
.release = sht20_release,
};
/* 软复位初始化sht20*/
static int sht20_init(struct i2c_client *client)
{
return 0;
}
/*CRC校验*/
static int crc_check(unsigned char *data, int len, unsigned char checksum)
{
}
/*读取sht20 的温度数据*/
static int read_temperature(struct i2c_client *client, unsigned char *buf)
{
return 0;
}
/*读取sht20 的湿度数据*/
static int read_humidity(struct i2c_client *client, unsigned char *buf)
{
return 0;
}
/*温度属性显示函数*/
static ssize_t sht20_temp_humi_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "temperature=%d humidity=%d\n", ((data[3] << 8) | data[2]), ((data[1] << 8) | data[0]) );//1000倍
}
/*sysfs - echo写入属性函数*/
static ssize_t sht20_temp_humi_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
return count;
}
/*初始化属性值*/
static DEVICE_ATTR(sht20_temp_humi, 0644, sht20_temp_humi_show, sht20_temp_humi_store);
/*i2c驱动probe函数,设备和驱动匹配后执行*/
static int sht20_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
/*i2c驱动的remove函数,移除时候执行*/
static int sht20_remove(struct i2c_client *client)
{
return 0;
}
/*传统方式ID列表*/
static const struct i2c_device_id sht20_id[] = {
{"sht20", 0},
{}
};
/*定义i2c设备结构体*/
static struct i2c_driver sht20_drv = {
.probe = sht20_probe,
.remove = sht20_remove,
.id_table = sht20_id,
};
/*注册sht20驱动*/
module_i2c_driver(sht20_drv);
MODULE_LICENSE("GPL");
- 第一步我们定义 i2c 总线设备结构体并实现 i2c 总线设备的注册和注销函数,在这里就是程驱动程序的入口和出口函数。
- 第二步实现 i2c 总线设备结构体中定义的操作函数,主要是.probe 匹配函数,在.probe函数中添加、注册一个字符设备,这个字符设备用于实现 sht20 的具体功能。
- 第三不定义并实现字符设备操作函数集。在应用程序中的 open、read 操作传到内核后就是执行这些函数,所以他们要真正实现对sht20的初始化以及读取转换结果。
- 具体的读、写 sht20 的函数,它们被第三部分的函数调用,用户自行定义。
三、sht20驱动开发
(1)硬件连接
(2)修改编译设备树
添加设备节点:
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
sht20-i2c@40{
compatible = "sht20";
reg = <0x40>;
};
};
&iomuxc {
pinctrl-names = "default";
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__I2C1_SDA 0x4001b8b0
MX6UL_PAD_GPIO1_IO02__I2C1_SCL 0x4001b8b0
>;
};
};
在源码目录下执行make dtbo
编译之后放在tftp目录下,然后可以tftp从网络启动设备树和zImage。不懂的可以看文章开头的文章。
重启之后我们在设备树日常的proc/device-tree
目录下是看不见i2c1设备树节点的,因为主节点在头文件imx6ul.dtsi
中已经定义好了。
linux-imx/arch/arm/boot/dts$ vim imx6ul.dtsi
:
i2c1: i2c@21a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
具体位置:
root@igkboard:/proc/device-tree# find -name i2c@21a0000
./soc/bus@2100000/i2c@21a0000
./__local_fixups__/soc/bus@2100000/i2c@21a0000
(3)编写驱动代码
#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h> // init和exit相关宏
#include <linux/kernel.h> // printk(),内核打印函数
#include <linux/delay.h> // 延时函数头文件
#include <linux/device.h> // 用于设备创建的函数头文件
#include <linux/fs.h> //和fops相关的头文件
#include <linux/cdev.h> //字符设备 初始化相关
#include <linux/version.h>
//#include <linux/ide.h>
#include <linux/gpio.h> //gpio子系统头文件
#include <linux/of_gpio.h> //gpio子系统和设备树相关
#include <linux/platform_device.h> //platform总线设备相关
#include <linux/err.h> //错误码相关
#include <linux/timer.h> //定时器相关
#include <linux/i2c.h> //i2c子系统相关
#define DEV_NAME "sht20" //设备名
#define SHT20_SOFERESET 0xFE // 软复位
#define SHT20_TEMPERATURE_NO_HOLD_CMD 0xF3 // 无主机模式触发温度测量
#define SHT20_HUMIDITY_NO_HOLD_CMD 0xF5 // 无主机模式触发湿度测量
#define SHT20_TEMPERATURE_HOLD_CMD 0xE3 // 主机模式触发温度测量
#define SHT20_HUMIDITY_HOLD_CMD 0xE5 // 主机模式触发湿度测量
#define CRC_MODEL 0x131
#define CRC_SUCCESS 0
#define CRC_FAIL 1
#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif
static int dev_major = DEV_MAJOR; //主机号
struct sht20_priv {
struct cdev cdev;
struct class *dev_class;
struct i2c_client *client;
struct device *dev;
};
/* @description: 初始化sht20
*
* @parm : client - i2c 设备
* @parm :
* @return: 0 successfully , !0 failure
*/
static int sht20_init(struct i2c_client *client)
{
int rv;
char data = SHT20_SOFERESET;
rv = i2c_master_send(client, &data, 1);
if(rv < 0)
{
dev_err(&client->dev, "i2c send init cmd failure.\n");
return -1;
}
msleep(50);
return 0;
}
static int crc_check(unsigned char *data, int len, unsigned char checksum)
{
unsigned char crc = 0x00;
int i, j;
for(i=0; i<len; i++)
{
crc ^= *data++;
for (j=0; j<8; j++)
{
if (crc & 0x80)
{
crc = (crc << 1) ^ CRC_MODEL;
}
else
{
crc = (crc << 1);
}
}
}
// printk("crc clu data : [%x]\n", crc);
if(checksum == crc)
{
return CRC_SUCCESS;
}
else
{
return CRC_FAIL;
}
}
/* @description: 读取sht20 的温度数据
*
* @parm : client - i2c 设备
* @parm : buf - 存储读取的数据
* @return: 0 successfully , !0 failure
*/
static int read_temperature(struct i2c_client *client, unsigned char *buf)
{
int rv = 0;
int temperature = 0;
unsigned char tmp[3] = {0};
char data = SHT20_TEMPERATURE_HOLD_CMD;
//形参判断
if(!client || !buf)
{
printk("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
//发送CMD
rv = i2c_master_send(client, &data, 1);
//rv = i2c_smbus_write_byte(client, SHT20_TEMPERATURE_NO_HOLD_CMD);
if(rv < 0)
{
dev_err(&client->dev, "i2c send tmper cmd failure.\n");
return -1;
}
//delay 85ms
msleep(85);
//读取数据
rv = i2c_master_recv(client, tmp, sizeof(tmp));
if(rv < 0)
{
dev_err(&client->dev, "i2c recv tmper data failure.\n");
return -1;
}
// printk("read temperature: tmp[0] %x tmp[1] %x ; crc : tmp[2] %x\n", tmp[0], tmp[1], tmp[2]); //验证 crc校验结果
//数据处理
temperature = (tmp[0] << 8) | (tmp[1]&0xFC);
temperature = ((temperature * 175720) >> 16) - 46850;
//printk("temperature : %d\n", temperature);
//TODO: 可以加上CRC校验
if(0 != crc_check(tmp, 2, tmp[2]))
{
dev_err(&client->dev, "tmperature data fails to pass cyclic redundancy check\n");
return -1;
}
buf[0] = temperature & 0xFF;
buf[1] = (temperature >> 8) & 0xFF;
return 0;
}
/* @description: 读取sht20 的相对数据数据
*
* @parm : client - i2c 设备/客户端
* @parm : buf - 存储读取的数据
* @return: 0 successfully , !0 failure
*/
static int read_humidity(struct i2c_client *client, unsigned char *buf)
{
int rv = 0;
int humidity = 0;
unsigned char tmp[3] = {0};
char data = SHT20_HUMIDITY_HOLD_CMD;
//形参判断
if(!client || !buf)
{
printk("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
//发送CMD
rv = i2c_master_send(client, &data, 1);
//rv = i2c_smbus_write_byte(client, SHT20_HUMIDITY_NO_HOLD_CMD);
if(rv < 0)
{
dev_err(&client->dev, "i2c send humidity cmd failure.\n");
return -1;
}
//delay 29ms
msleep(29);
//读取数据
rv = i2c_master_recv(client, tmp, sizeof(tmp));
if(rv < 0)
{
dev_err(&client->dev, "i2c recv humidity data failure.\n");
return -1;
}
// printk("read humidity: tmp[0] %x tmp[1] %x ; crc : tmp[2] %x\n", tmp[0], tmp[1], tmp[2]);
//数据处理
humidity = (tmp[0] << 8) | (tmp[1]&0xFC);
humidity = ((humidity * 125000) >> 16) - 6000;
//crc-8 校验
if(0 != crc_check(tmp, 2, tmp[2]))
{
dev_err(&client->dev, "tmperature data fails to pass cyclic redundancy check\n");
return -1;
}
buf[0] = humidity & 0xFF;
buf[1] = (humidity >> 8) & 0xFF;
return 0;
}
/* @description: sht20 打开设备
*
* @parm : inode - 传递给驱动的inode
* @parm : filp - 设备文件,利用其私有数据成员
* @return: 0 successfully , !0 failure
*/
static int sht20_open(struct inode *inode, struct file *filp)
{
struct sht20_priv *priv = container_of(inode->i_cdev, struct sht20_priv, cdev);
int rv;
//初始化sht20
rv = sht20_init(priv->client);
if(rv < 0)
{
dev_err(priv->dev, "sht20 init failure.\n");
}
//dev_info(priv->dev, "sht20 init successfully.\n");
filp->private_data = priv;
return 0;
}
/* @description: 从设备读取文件
*
* @parm : filp - 设备文件,文件描述符
* @parm : buf - 返回给用户空间的数据缓冲区
* @parm : cnt - 要读取的数据长度
* @parm : offt - 相对于文件首地址的偏移
* @return: 读取的字节数,负数 - 读取失败
*/
static ssize_t sht20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int rv = 0;
//struct i2c_client *client = filp->private_data;
struct sht20_priv *priv = filp->private_data;
unsigned char data[4] = {0,};
if(!priv->client)
{
printk("failure to get i2c_client.\n");
return -EFAULT;
}
rv = read_temperature(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_temperature failure.\n");
}
data[3] = data[1];
data[2] = data[0];
rv = read_humidity(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_humidity failure.\n");
}
//printk("test %x %x %x %X \n", data[3], data[2], data[1], data[0]);
rv = copy_to_user(buf, data, sizeof(data));
if(rv)
{
dev_err(priv->dev, "copy to user error.\n");
return -EFAULT;
}
return sizeof(data);
}
/* @description: 关闭设备
*
* @parm : inode - 传递给驱动的inode
* @parm : filp - 设备文件,file结构体有个私有数据区可以使用
* @return: 0 successfully , !0 failure
*/
static int sht20_release(struct inode *inode, struct file *filp)
{
return 0;
}
//设备操作函数
static struct file_operations sht20_fops = {
.owner = THIS_MODULE,
.open = sht20_open,
.read = sht20_read,
.release = sht20_release,
};
/* @description: sysfs - 温度属性显示函数
*
* @parm : dev - 设备指针,创建file时候会指定dev
* @parm : attr - 设备属性,创建时候传入
* @parm : buf - 传出给sysfs中显示的buf
* @return: 显示的字节数
* @TODO: 函数不够正规,了解PAGE_SIZE
*/
static ssize_t sht20_temp_humi_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sht20_priv *priv = dev_get_drvdata(dev);
int rv = 0;
unsigned char data[4] = {0,};
if(!priv->client)
{
printk("failure to get i2c_client.\n");
return -EFAULT;
}
rv = read_temperature(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_temperature failure.\n");
}
data[3] = data[1];
data[2] = data[0];
rv = read_humidity(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_humidity failure.\n");
}
//printk("test %x %x %x %X \n", data[3], data[2], data[1], data[0]);
return sprintf(buf, "temperature=%d humidity=%d\n", ((data[3] << 8) | data[2]), ((data[1] << 8) | data[0]) );//1000倍
}
/* @description: sysfs - echo写入属性函数
*
* @parm : dev - 设备指针,创建file时候会指定dev
* @parm : attr - 设备属性,创建时候传入
* @parm : buf - 用户空间的buf
* @parm : count - 传入buf的size
* @return: 写入的buf大小
*/
static ssize_t sht20_temp_humi_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
char k_buf[10] = {0,};
snprintf(k_buf, sizeof(k_buf), "%s", buf);
dev_info(dev, "Don't echo to me -> [%s] size [%d]\n", k_buf, count);
return count;
}
//初始化属性值
static DEVICE_ATTR(sht20_temp_humi, 0644, sht20_temp_humi_show, sht20_temp_humi_store);
/* @description: i2c驱动probe函数,设备和驱动匹配后执行
*
* @parm : client - i2c设备
* @parm : id - i2c设备ID
* @return: 0 successfully , !0 failure
*/
static int sht20_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct sht20_priv *priv = NULL;
//struct device *dev;
dev_t devno; //设备号
int rv = 0;
//0.给priv分配空间
priv = devm_kzalloc(&client->dev, sizeof(struct sht20_priv), GFP_KERNEL);
if(!priv)
{
return -ENOMEM;
}
//1.创建设备号
if(0 != dev_major)
{
devno = MKDEV(dev_major, 0);
rv = register_chrdev_region(devno, 1, DEV_NAME); //静态创建
}
else
{
rv = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);//动态创建
dev_major = MAJOR(devno);//获主设备号
}
if(rv < 0)
{
dev_err(&client->dev, "%s driver can't get major %d\n", DEV_NAME, dev_major);
return rv;
}
//2.注册字符设备
cdev_init(&priv->cdev, &sht20_fops); //初始化cdev
priv->cdev.owner = THIS_MODULE;
rv = cdev_add(&priv->cdev, devno, 1);
if(0 != rv)
{
dev_err(&client->dev, "error %d add %s device failure.\n", rv, DEV_NAME);
goto undo_major;
}
//3.创建类,驱动进行节点创建
priv->dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(priv->dev_class))
{
dev_err(&client->dev, "%s driver create class failure.\n", DEV_NAME);
rv = -ENOMEM;
goto undo_cdev;
}
//4.创建设备
priv->dev = device_create(priv->dev_class, NULL, devno, NULL, DEV_NAME);
if(IS_ERR(priv->dev))
{
rv = -ENOMEM;
goto undo_class;
}
//5. 创建sys 属性 在platform下
if(device_create_file(priv->dev, &dev_attr_sht20_temp_humi))
{
rv = -ENOMEM;
goto undo_device;
}
//6. 保存私有数据
priv->client = client;
i2c_set_clientdata(client, priv);
dev_set_drvdata(priv->dev, priv);
dev_info(&client->dev, "sht20 i2c driver probe okay.\n");
return 0;
undo_device:
device_destroy(priv->dev_class, devno);
undo_class:
class_destroy(priv->dev_class);
undo_cdev:
cdev_del(&priv->cdev);
undo_major:
unregister_chrdev_region(devno, 1);
devm_kfree(&client->dev, priv);
return rv;
}
/* @description: i2c驱动的remove函数,移除时候执行
*
* @parm : client - i2c
* @parm :
* @return: 0 successfully , !0 failure
*/
static int sht20_remove(struct i2c_client *client)
{
struct sht20_priv *priv = i2c_get_clientdata(client);
dev_t devno = MKDEV(dev_major, 0);
//删除sys中的属性
device_remove_file(priv->dev, &dev_attr_sht20_temp_humi);
//设备销毁
device_destroy(priv->dev_class, devno);
//注销类
class_destroy(priv->dev_class);
//删除字符设备
cdev_del(&priv->cdev);
unregister_chrdev_region(devno, 1);
//释放堆
devm_kfree(&client->dev, priv);
dev_info(&client->dev, "sht20 driver remove.\n");
return 0;
}
//传统方式ID列表
static const struct i2c_device_id sht20_id[] = {
{"sht20", 0},
{}
};
//设备树匹配列表
static const struct of_device_id of_sht20_match [] = {
{.compatible="sht20"},
{}
};
MODULE_DEVICE_TABLE(of, of_sht20_match);
static struct i2c_driver sht20_drv = {
.probe = sht20_probe,
.remove = sht20_remove,
.driver = {
.name = "sht20 driver 0.1", //name 不重要
.owner = THIS_MODULE,
.of_match_table = of_sht20_match,
},
.id_table = sht20_id,
};
//注册sht20驱动
module_i2c_driver(sht20_drv);
MODULE_AUTHOR("Wei Huihong <weihuihui586@gmail.com>");
MODULE_DESCRIPTION("i.MX6ULL sht20 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("i2c:imx_sht20_i2c_driver");
测试代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV_STH20 "/dev/sht20"
int main(int argc, char *argv[])
{
float temperature = 0.0;
float humidity = 0.0;
unsigned char data[4] = {0,};
int rv = -1;
int fd_sht20 = -1;
fd_sht20 = open(DEV_STH20, O_RDONLY);
if(fd_sht20 < 0)
{
printf("open %s failure.\n", DEV_STH20);
return -1;
}
printf("open %s successful.\n", DEV_STH20);
while(1)
{
//printf("start read temperature and humidity.\n");
memset(data, 0, sizeof(data));
rv = read(fd_sht20, data, sizeof(data));
if(rv < 0)
{
printf("read data failure.\n");
}
else
{
//printf("data[0-3] : %x %x %x %x\n",data[0], data[1], data[2], data[3]);
humidity = ((data[1] << 8) | data[0]) / 1000.0;
temperature = ((data[3] << 8) | data[2]) /1000.0;
printf("temperature : %.4f℃ humidity : %.4f %%\n", temperature, humidity);
}
sleep(5);
}
close(fd_sht20);
return 0;
}
Makefile:
LINUX_SRC = /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
CROSS_COMPILE=arm-linux-gnueabihf-
INST_PATH=/tftp
APP=sht20_app
PWD := $(shell pwd)
EXTRA_CFLAGS+=-DMODULE
obj-m += sht20_i2c_drv.o
modules:
@make clean
@echo ${LINUX_SRC}
@make -C $(LINUX_SRC) M=$(PWD) modules
@make clear
@$(CROSS_COMPILE)gcc $(APP).c -o $(APP)
uninstall:
rm -f ${INST_PATH}/*.ko
install: uninstall
cp -af *.ko ${INST_PATH}
clear:
@rm -f *.o *.cmd *.mod.c .*.cmd *.mod
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
clean: clear
@rm -f *.ko
@rm -f $(APP)
生成驱动文件以及可执行文件:
wangdengtao@wangdengtao-virtual-machine:~/ldd_learning_code/LDD4_I2C$ make
make[1]: 进入目录“/home/wangdengtao/ldd_learning_code/LDD4_I2C”
make[1]: 离开目录“/home/wangdengtao/ldd_learning_code/LDD4_I2C”
/home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
make[1]: 进入目录“/home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx”
CC [M] /home/wangdengtao/ldd_learning_code/LDD4_I2C/sht20_i2c_drv.o
MODPOST /home/wangdengtao/ldd_learning_code/LDD4_I2C/Module.symvers
CC [M] /home/wangdengtao/ldd_learning_code/LDD4_I2C/sht20_i2c_drv.mod.o
LD [M] /home/wangdengtao/ldd_learning_code/LDD4_I2C/sht20_i2c_drv.ko
make[1]: 离开目录“/home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx”
make[1]: 进入目录“/home/wangdengtao/ldd_learning_code/LDD4_I2C”
make[1]: 离开目录“/home/wangdengtao/ldd_learning_code/LDD4_I2C”
wangdengtao@wangdengtao-virtual-machine:~/ldd_learning_code/LDD4_I2C$ ls
Makefile sht20_app sht20_app.c sht20_i2c_drv.c sht20_i2c_drv.ko
(4)执行结果
将我们编译生成的驱动文件以及测试可执行文件上传到我们的开发板:
root@igkboard:~# tftp -gr sht20_i2c_drv.ko 192.168.137.224
root@igkboard:~# tftp -gr sht20_app 192.168.137.224
root@igkboard:~# chmod a+x sht20_app
安装驱动完成之后可以看见我们的设备节点(sht20
)了:
root@igkboard:~# insmod sht20_i2c_drv.ko
root@igkboard:~# lsmod
Module Size Used by
sht20_i2c_drv 16384 0
rtl8188fu 999424 0
imx_rngc 16384 0
rng_core 20480 1 imx_rngc
secvio 16384 0
error 20480 1 secvio
root@igkboard:~# ls -l /dev/sht20
crw------- 1 root root 243, 0 Apr 25 06:52 /dev/sht20
并且我们到路径、sys/class/sht20/sht20/
路径下,可以看见我们的温度湿度值:
root@igkboard:/sys/class/sht20/sht20# ls
dev power sht20_temp_humi subsystem uevent
root@igkboard:/sys/class/sht20/sht20# cat sht20_temp_humi
temperature=20374 humidity=59147
执行可执行文件:
root@igkboard:~# ./sht20_app
open /dev/sht20 successful.
temperature : 20.1810℃ humidity : 60.7110 %
temperature : 20.1810℃ humidity : 60.7950 %
temperature : 22.2730℃ humidity : 14.6530 %
temperature : 24.1930℃ humidity : 26.8060 %
temperature : 22.9160℃ humidity : 29.9120 %
temperature : 23.0880℃ humidity : 29.9650 %
temperature : 26.5740℃ humidity : 27.3020 %
root@igkboard:~# rmmod sht20_i2c_drv.ko
root@igkboard:~# dmesg | tail -2
[ 59.500836] sht20 driver 0.1 0-0040: sht20 i2c driver probe okay.
[ 331.569054] sht20 driver 0.1 0-0040: sht20 driver remove.
四、重难点分析
(1)Linux下DEVICE_ATTR介绍
我们为什么能够在sys/class/sht20/sht20/
目录下看见我们的温度呢?
使用DEVICE_ATTR,可以实现驱动在sys目录自动创建文件,我们只需要实现show和store函数即可。然后在应用层就能通过cat和echo命令来对sys创建出来的文件进行读写驱动设备,实现交互。
【1】DEVICE_ATTR宏定义
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
DEVICE_ATTR(_name, _mode, _show, _store)
- _name:名称,也就是将在sysfs中生成的文件名称。
- _mode:上述文件的访问权限,与普通文件相同,UGO的格式。
- _show:显示函数,cat该文件时,此函数被调用。
- _store:写函数,echo内容到该文件时,此函数被调用。
【2】代码实现
这是上述驱动的代码,后面测试讲解。我们的show函数可以cat查看我们的内容,但是store函数表示我的这个文件不允许写内容进去修改。
/* @description: sysfs - 温度属性显示函数
*
* @parm : dev - 设备指针,创建file时候会指定dev
* @parm : attr - 设备属性,创建时候传入
* @parm : buf - 传出给sysfs中显示的buf
* @return: 显示的字节数
* @TODO: 函数不够正规,了解PAGE_SIZE
*/
static ssize_t sht20_temp_humi_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sht20_priv *priv = dev_get_drvdata(dev);
int rv = 0;
unsigned char data[4] = {0,};
if(!priv->client)
{
printk("failure to get i2c_client.\n");
return -EFAULT;
}
rv = read_temperature(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_temperature failure.\n");
}
data[3] = data[1];
data[2] = data[0];
rv = read_humidity(priv->client, data);
if(rv)
{
dev_err(priv->dev, "read_humidity failure.\n");
}
//printk("test %x %x %x %X \n", data[3], data[2], data[1], data[0]);
return sprintf(buf, "temperature=%d humidity=%d\n", ((data[3] << 8) | data[2]), ((data[1] << 8) | data[0]) );//1000倍
}
/* @description: sysfs - echo写入属性函数
*
* @parm : dev - 设备指针,创建file时候会指定dev
* @parm : attr - 设备属性,创建时候传入
* @parm : buf - 用户空间的buf
* @parm : count - 传入buf的size
* @return: 写入的buf大小
*/
static ssize_t sht20_temp_humi_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
char k_buf[10] = {0,};
snprintf(k_buf, sizeof(k_buf), "%s", buf);
dev_info(dev, "Don't echo to me -> [%s] size [%d]\n", k_buf, count);
return count;
}
//初始化属性值
static DEVICE_ATTR(sht20_temp_humi, 0644, sht20_temp_humi_show, sht20_temp_humi_store);
【3】测试讲解
前面我们已经安装了我们的驱动,我们去sys/class/sht20/sht20/
目录下测试一下能不能写数据进去。
读数据成功:
root@igkboard:/sys/class/sht20/sht20# cat sht20_temp_humi
temperature=20503 humidity=59635
写数据失败:
root@igkboard:/sys/class/sht20/sht20# echo 123 > sht20_temp_humi
root@igkboard:/sys/class/sht20/sht20# dmesg | tail -1
root@igkboard:/sys/class/sht20/sht20# dmesg | tail -2
[ 3651.603397] sht20 sht20: Don't echo to me -> [123
] size [4]
(2)container_of介绍
【1】作用介绍
container_of的主要作用是:通过已知的一个数据结构成员指针ptr,数据结构类型type,以及这个成员指针在这个数据结构中的成员名member,来获取指向这个数据结构的指针type *。
/**
* container_of - 通过三个参数,返回指向数据结构的指针
* @ptr: 数据结构中指向某一成员的指针
* @type: 数据结构类型
* @member: 在数据结构中的成员
*
*/
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
【2】举个栗子
/*********************************************************************************
* Copyright: (C) 2023 WangDengtao<1799055460@qq.com>
* All rights reserved.
*
* Filename: container.c
* Description: This file
*
* Version: 1.0.0(2023年04月23日)
* Author: WangDengtao <1799055460@qq.com>
* ChangeLog: 1, Release initial version on "2023年04月23日 17时14分31秒"
*
********************************************************************************/
#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct profile
{
char *name;
unsigned int age;
char *sex;
}profile_t;
static profile_t prop = {
.name = "WangDengtao",
.age = 23,
.sex = "male"
};
int main()
{
profile_t *p = container_of(&prop.sex, struct profile, sex);
printf("name = %s, age = %u, sex = %s\n", p->name, p->age, p->sex);
printf("This number is = %d\n", ({1;2;3;4;}));
return 0;
}
wangdengtao@wangdengtao-virtual-machine:~$ gcc container.c
wangdengtao@wangdengtao-virtual-machine:~$ ./a.out
name = WangDengtao, age = 23, sex = male
This number is = 4