I2C驱动框架介绍以及Linux下sht20驱动开发温湿度传感器获取温湿度

news2024/11/25 4:23:31

文章目录

    • 一、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_adapteri2c_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_clienti2c_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker容器---Harbor私有仓库部署与管理

Harbor私有仓库部署与管理 一、Harbor概述二、Harbor特性三、Harbor构成四、Harbor构建Docker私有仓库1、部署docker-compos2、下载或上传 Harbor 安装程序3、启动Harbor4、查看Harbor启动镜像5、浏览器访问创建一个新项目6、通过127.0.0.1来登录和推送镜像7、在客户端上传镜像…

【JavaScript】动态表格

&#x1f38a;专栏【 前端易错合集】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; &#x1f354;介绍 就是在输入框中输入数字后&#xff0c;再按…

Linux安装mysql(5.7解压版)

Linux服务器安装软件时&#xff0c;建议安装解压版&#xff0c;将文件安装在自己指定的目录。安装版一般会将软件安装在Linux默认的目录&#xff0c;如/usr/local/&#xff0c;配置文件在/etc/&#xff0c;日志在/logs&#xff0c;安装目录比较分散&#xff0c;特别是不熟悉该软…

RabbitMQ --- SpringAMQP

一、简介 SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址&#xff1a;Spring AMQP SpringAMQP提供了三个功能&#xff1a; 自动声明队列、交换机及其绑定关系 基于注解的监…

代码随想录算法训练营第四十二天|01背包问题,你该了解这些!、01背包问题,你该了解这些! 滚动数组 、416. 分割等和子集

文章目录 01背包问题&#xff0c;你该了解这些&#xff01;01背包问题&#xff0c;你该了解这些&#xff01; 滚动数组416. 分割等和子集 01背包问题&#xff0c;你该了解这些&#xff01; 题目链接&#xff1a;代码随想录 二维数组解决0-1背包问题 解题思路&#xff1a; 1.dp…

tensorflow ---gpu的环境配置

1. CUDA配置&#xff1a; 先鼠标右键查看对应的CUDA的版本&#xff1a; 2.安装CUDA&#xff1a; 1.点击 CUDA去官网来下载安装包。 这里我选择的是我电脑支持的9.0的版本&#xff1a; 2.下载CUDA安装包&#xff1a; 3. 安装CUDA的过程&#xff1a; 自定义安装路径&#xff…

RabbitMQ --- 简介、快速入门

一、初识MQ 1.1、同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;但…

【论文精度(李沐老师)】Deep Residual Learning for Image Recognition

Deep Residual Learning for Image Recognition 残差连接主要干的一个事情是&#xff1a; 如果你新加的层不能让你的模型变好的时候&#xff0c;因为有残差连接的存在&#xff0c;可以使新加的那些层不会学到任何东西。 Abstract &#xff08;提出问题&#xff09;深的神经网…

STM32-HAL-定时器(无源蜂鸣器的驱动)

文章目录 一、蜂鸣器的介绍二、常用的无源蜂鸣器的电路三、测试准备四、初始化片上外设4.1 初始化定时器4的通道2为PWM输出模式4.2 编写驱动代码4.3 Logic分析仪查看波形4.4 代码分析 一、蜂鸣器的介绍 有源蜂鸣器&#xff1a; 有源蜂鸣器内部有一个发声电路,也就是“源”&…

程序员那些“越早知道越好的”道理

目录 程序员那些“越早知道越好的”道理1、学会阅读文档2、学会调试代码3、学会使用版本控制工具4、学会编写测试代码5、学会提问6、学会使用搜索引擎7、学会读懂源代码 程序员那些“越早知道越好的”道理 作为一名程序员&#xff0c;有很多话想对新手说&#xff0c;因为这些话…

ctr特征重要性建模:FiBiNetFiBiNet++模型

FiBiNET&#xff08;Feature Importance and Bilinear feature Interaction NETwork&#xff09;为推荐系统的CTR模型提出了一些创新方向&#xff1a; 引入一个SENet模块&#xff0c;可以动态学习特征的重要性&#xff1b;引入一个双线性模块&#xff08;Bilinear-Interaction…

在当前互联网行情下,Android想转音视频开发,会有前景吗?

前言 近年来&#xff0c;由于三年疫情的影响&#xff0c;很多公司都开始陆陆续续的在裁员&#xff0c;Android开发工作岗位也是&#xff0c;可能有些从事Android开发的朋友还没有意识到&#xff0c;Android开发岗位正在变少&#xff0c;求职者&#xff0c;僧多粥少&#xff0c…

数据可视化大屏电商数据展示平台开发实录(Echarts柱图曲线图、mysql筛选统计语句、时间计算、大数据量统计)

数据可视化大屏电商数据展示平台 一、前言二、项目介绍三、项目展示四、项目经验分享4.1 翻牌器4.1.1 翻牌器-今日实时交易4.1.2.翻牌器后端统计SUM函数的使用 4.2 不同时间指标的数据MySql内部的时间计算 4.3 实时交易播报MySql联表查询和内部遍历循环 4.4 每日交易量4.4.1.近…

Flutter开发日常练习-小猫咪杂货店(新增欢迎页,广告页和侧滑页面)

养小猫咪的伙伴来我的店铺逛逛吧!抖音商城搜索#早睡早起的猫咪小铺子 Flutter开发日常练习-小猫咪杂货店(新增动画和跳转抖音)_workersJiaDa的博客-CSDN博客URL Launcher是一个Flutter插件&#xff0c;它允许您的应用程序启动网络浏览器、地图应用程序、拨号器应用程序、邮件应…

Object Manager中的Hierarchy Columns

【前言】&#xff1a;最近偶然发现Object Manager中多了一项Hierarchy Columns&#xff0c;正好在做Case Mgmt这块的业务&#xff0c;需要做Case Hierarchy&#xff0c;或许熟悉这个新概念对后续方案的落地有一定启发。 #1. Account Hierarchy - 这个是标准功能&#xff0c;Acc…

【Winform学习笔记(二)】TextBox文本框实现按回车键触发Button事件

TextBox文本框实现按回车键触发Button事件 前言正文1、实现方法2、具体代码3、实现效果 前言 在本文中主要介绍 如何基于 Winform 框架实现 TextBox 文本框实现按回车键触发 Button 事件&#xff0c;该功能可实现在文本框中输入密码后不需要按登录或确定按钮&#xff0c;直接回…

如果建立一个由AI组成的社会……

你有没有想过&#xff0c;如果我们建立一个完全由AI组成的公民社会团体&#xff0c;让它们模仿人类的文明发展&#xff0c;那么这个AI社会最终将会进化到何种文明程度&#xff1f;需要明确的是AI社会只有AI&#xff0c;没有人类&#xff0c;完全是AI之间互相沟通交流&#xff0…

制作剧本杀小游戏系统

制作剧本杀小游戏软件的功能可以包括以下几点&#xff1a; 角色设定和分配&#xff1a;提供多种角色供玩家选择&#xff0c;根据玩家数量随机分配角色。 剧情框架&#xff1a;提供预设的剧情框架&#xff0c;或者允许用户自定义剧情。 背景设定&#xff1a;提供游戏…

图像处理:高斯滤波算法

目录 前言 概念介绍 基本原理 卷积核的大小 卷积核的形状和权重比 卷积核的归一化 结论 Opencv实现高斯滤波 Python手写实现高斯滤波 参考文章 前言 在此之前&#xff0c;我曾在此篇中推导过图像处理&#xff1a;推导五种滤波算法&#xff08;均值、中值、高斯、双边…

linux 命令之 tar -czvf和 tar -xzvf

文章目录 一、概述&#xff1a;二、基础知识 一、概述&#xff1a; tar 用于linux 系统中压缩和解压 二、基础知识 tar常用命令参数说明 tar命令的czvf/xzvf参数分别代表的意义如下&#xff1a; -c 或–create 建立新的备份文件。 -x或–extract或–get 从备份文件中还原文件…