【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第172章 使用C文件编写I2C client代码

news2024/10/12 11:32:29

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


第172章 使用C文件编写I2C client代码

首先我们来回顾一下前面讲解的平台总线相关知识,平台总线将驱动分为了platform driver和platform device两个部分,而最终设备树取代了platform device部分,而为了对I2C子系统框架有一个更深刻的认识,在本章节将使用platform device重新编写I2C client代码并与上一章中的I2C driver部分进行匹配。

172.1 I2C硬件资源描述

172.1.1 i2c_get_adapter函数

i2c_get_adapter函数的主要作用是根据给定的I2C适配器编号 nr 从 i2c_adapter_idr 中查找对应的 i2c_adapter 结构体,该函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

struct i2c_adapter *i2c_get_adapter(int nr)
{
    struct i2c_adapter *adapter;

    // 获取 i2c_adapter_idr 中的锁
    mutex_lock(&core_lock);

    // 在 i2c_adapter_idr 中查找指定编号的适配器
    adapter = idr_find(&i2c_adapter_idr, nr);
    if (!adapter)
        goto exit;

    // 尝试获取适配器所属模块的引用计数
    if (try_module_get(adapter->owner))
        // 增加适配器 device 的引用计数
        get_device(&adapter->dev);
    else
        adapter = NULL;

exit:
    // 释放 i2c_adapter_idr 中的锁
    mutex_unlock(&core_lock);

    return adapter;
}

第6行:获取 core_lock 锁,以防止其他线程同时访问 i2c_adapter_idr。

第9-11行:使用 idr_find() 函数在 i2c_adapter_idr 中查找指定编号的适配器。

第14-18行如果找到了适配器,就尝试获取适配器所属模块的引用计数,以防止模块被卸载。如果成功获取,就增加适配器 device 的引用计数。如果失败,就将 adapter 设置为 NULL。

172.1.2 i2c_put_adapter函数

上一小节的i2c_get_adapter函数用于查找i2c_adapter结构体,当驱动卸载时2c_adapter结构体需要被释放,而当结构体i2c_put_adapter函数用于释放 i2c_adapter 结构体,i2c_put_adapter函数也定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

void i2c_put_adapter(struct i2c_adapter *adap)
{
    // 如果 adap 指针为 NULL,直接返回
    if (!adap)
        return;

    put_device(&adap->dev);    // 调用 put_device 函数释放 i2c_adapter 设备
    module_put(adap->owner);    // 减少 i2c_adapter 所属模块的引用计数
}

172.1.3 i2c_new_device函数

i2c_new_device函数用于创建和注册与 I2C 总线上对应的设备。注册完成后,I2C子系统会自动为该设备创建相应的设备节点,供上层应用程序进行访问和控制,该函数同样定义在定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:、

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client       *client;
        int                     status;

        // 分配 i2c_client 结构体空间
        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (!client)
                return NULL;

        // 设置 i2c_client 的适配器指针
        client->adapter = adap;

        // 从 i2c_board_info 结构体中拷贝相关信息到 i2c_client
        client->dev.platform_data = info->platform_data;
        client->flags = info->flags;
        client->addr = info->addr;
        client->init_irq = info->irq;
        if (!client->init_irq)
                client->init_irq = i2c_dev_irq_from_resources(info->resources,
                                                         info->num_resources);
        client->irq = client->init_irq;
        strlcpy(client->name, info->type, sizeof(client->name));

        // 检查地址是否有效
        status = i2c_check_addr_validity(client->addr, client->flags);
        if (status) {
                dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                        client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
                goto out_err_silent;
        }

        // 检查地址是否已被其他设备占用
        status = i2c_check_addr_ex(adap, i2c_encode_flags_to_addr(client));
        if (status)
                dev_err(&adap->dev,
                        "%d i2c clients have been registered at 0x%02x",
                        status, client->addr);

        // 设置 i2c_client 的设备信息
        client->dev.parent = &client->adapter->dev;
        client->dev.bus = &i2c_bus_type;
        client->dev.type = &i2c_client_type;
        client->dev.of_node = of_node_get(info->of_node);
        client->dev.fwnode = info->fwnode;
        i2c_dev_set_name(adap, client, info, status);

        // 如果有设备属性,添加到设备
        if (info->properties) {
                status = device_add_properties(&client->dev, info->properties);
                if (status) {
                        dev_err(&adap->dev,
                                "Failed to add properties to client %s: %d\n",
                                client->name, status);
                        goto out_err_put_of_node;
                }
        }

        // 注册设备
        status = device_register(&client->dev);
        if (status)
                goto out_free_props;

        dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
                client->name, dev_name(&client->dev));

        return client;

out_free_props:
        if (info->properties)
                device_remove_properties(&client->dev);
out_err_put_of_node:
        of_node_put(info->of_node);
out_err_silent:
        kfree(client);
        return NULL;
}

第6-9行:动态分配 i2c_client 结构体空间,使用 kzalloc() 动态分配一个 i2c_client 结构体的内存空间,并将其清零。

第12行:设置 i2c_client 的适配器指针,将传入的 i2c_adapter 指针保存到 i2c_client 的 adapter 字段中。

第15-23行:从 i2c_board_info 复制信息到 i2c_client,将输入的 i2c_board_info 结构体中的相关信息,如设备地址(addr)、设备标志(flags)、中断号(irq)等,复制到新创建的 i2c_client 结构体中。

第26-38检查地址合法性:调用 i2c_check_addr_validity() 和 i2c_check_addr_ex() 函数检查设备地址的有效性和是否与其他设备冲突。

第40-46行:设置 i2c_client 的其他信息,初始化 i2c_client 的其他字段,如设备名称(name)、设备节点(of_node)、设备属性(properties)等。

第60-65行:注册 i2c_client 设备,最后,调用 device_register() 函数将新创建的 i2c_client 设备注册到内核设备模型中。

172.1.4 i2c_board_info 结构体

i2c_new_device函数会传入一个i2c_board_info类型的结构体,该结构体描述了I2C设备的静态信息,如设备类型、地址、名称等,该结构体定义在“include/linux/i2c.h”文件中,具体内容如下所示:

struct i2c_board_info {
    char type[I2C_NAME_SIZE];    // I2C 设备的类型名称,最大长度为 I2C_NAME_SIZE
    unsigned short flags;    // I2C 设备的标志位,用于指定设备的特殊属性
    unsigned short addr;    // I2C 设备的地址
    const char *dev_name;    // I2C 设备的设备名称
    void *platform_data;    // I2C 设备的平台数据,可为 NULL
    struct device_node *of_node;    // I2C 设备节点在设备树中的节点指针
    struct fwnode_handle *fwnode;    // I2C 设备节点在 ACPI 中的 fwnode 句柄
    const struct property_entry *properties;    // I2C 设备的属性列表
    const struct resource *resources;    // I2C 设备使用的资源列表
    unsigned int num_resources;    // I2C 设备使用的资源数量
    int irq;
};

172.2驱动程序的编写

本实验驱动对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\103_ft5x06_02\

本实验旨在使用platform device编写I2C client部分代码,编写完成的ft5x06_device.c代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>

// 定义一个 i2c_adapter 结构体指针
struct i2c_adapter *i2c_ada;

// 定义 i2c_board_info 结构体数组,用于描述 ft5x06 设备
static struct i2c_board_info ft5x06[] = {
    {
        .type = "my-ft5x06",
        .addr = 0x38, // ft5x06 设备的 I2C 地址
    },
};

// 驱动的初始化函数
static int ft5x06_client_init(void)
{
    // 获取 I2C 适配器
    i2c_ada = i2c_get_adapter(1);
    if (!i2c_ada)
    {
        printk(KERN_ERR "Failed to get I2C adapter\n");
        return -ENODEV;
    }

    // 注册 ft5x06 设备
    i2c_new_device(i2c_ada, ft5x06);

    return 0;
}

// 驱动的退出函数
static void ft5x06_client_exit(void)
{
     // 释放 I2C 适配器
    i2c_put_adapter(i2c_ada);
}

// 驱动的初始化和退出入口函数
module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);

MODULE_LICENSE("GPL");

platform device驱动编写完成之后,因为匹配方式不同,所以并不能直接使用上一章节编写的I2C驱动程序,需要对驱动程序进行简单的修改,修改完成的t5x06_driver.c I2C驱动程序如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of_device.h>

// ft5x06设备的初始化函数
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    printk("This is ft5x06 probe\n");
    return 0;
}

// ft5x06设备的移除函数
int ft5x06_remove(struct i2c_client *client) {
    return 0;
}

// 定义 i2c_device_id 结构体数组,用于标识 ft5x06 设备
static const struct i2c_device_id ft5x06_id[] = {
    { "my-ft5x06", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, ft5x06_id);

// 定义 i2c_driver 结构体,描述 ft5x06 设备驱动
static struct i2c_driver ft5x06_driver = {
    .driver = {
        .name   = "my-ft5x06",
        .owner  = THIS_MODULE,
    },
    .probe      = ft5x06_probe,
    .remove     = ft5x06_remove,
    .id_table   = ft5x06_id,
};

// 驱动初始化函数
static int __init ft5x06_driver_init(void) {
    int ret;
    // 注册I2C设备驱动
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    return 0;
}

// 驱动退出函数
static void __exit ft5x06_driver_exit(void) {
    // 注销I2C设备驱动
    i2c_del_driver(&ft5x06_driver);
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

172.3 运行测试

172.3.1 编译驱动程序

首先在上一小节中的ft5x06_device.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += ft5x06_device.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放platform_driver.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成ft5x06_driver.ko目标文件,如下图所示: 

然后在上一小节中的ft5x06_driver.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示: 对于Makefile的内容注释已在上图添加,保存退出之后,来到存放platform_driver.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示: 

编译完生成ft5x06_driver.ko目标文件,如下图所示: 

172.3.2 运行测试

首先启动开发板,进入系统之后如下图所示:

 

然后将上一个小节编译完成的ko文件拷贝到开发板上,拷贝完成如下图所示: 

然后使用以下命令加载两个驱动,加载完成如下图所示:

insmod ft5x06_device.ko

insmod ft5x06_driver.ko

可以看到在ft5x06_device.ko驱动加载成功之后,成功在I2C1控制器注册了地址为0x38的设备,然后加载了ft5x06_device.ko驱动,成功打印了在probe函数中的打印,证明platform device和ft5x06_driver驱动匹配成功了,而这里打印了两遍probe函数是因为驱动还会跟设备树进行匹配,一般情况下只使用设备树这一硬件描述方式,本章学习的platform device这一方式大家稍作了解即可。

然后使用以下命令进行驱动模块的卸载,如下图所示:

rmmod ft5x06_driver.ko
rmmod ft5x06_device.ko

由于没有在remove卸载函数中添加打印相关内容,所以使用rmmod命令卸载驱动之后,没有任何打印,至此,platform device I2C驱动实验就完成了。

 

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

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

相关文章

什么是CSV?超详细+通俗易懂版!!

CSV&#xff0c;全称为Comma-Separated Values&#xff08;逗号分隔值&#xff09;&#xff0c;是一种常用的文本文件格式&#xff0c;用于存储表格数据&#xff0c;如电子表格或数据库。CSV文件由一行或多行文本组成&#xff0c;每行文本包含由逗号分隔的一个或多个字段。这些…

【S2-MLP】核心方法解读

abstract&#xff1a; 近年来&#xff0c;visual Transformer (ViT)及其后续工作抛弃了卷积&#xff0c;利用自关注运算&#xff0c;达到了与CNN相当甚至更高的准确率。最近&#xff0c;MLP-mixer放弃了卷积和自关注操作&#xff0c;提出了一个只包含MLP层的体系结构。为了实现…

漏洞挖掘 | 通过错误日志实现XXE外带

介绍 在最近的一个项目中&#xff0c;我发现了一个与 XML 外部实体&#xff08;XXE&#xff09;攻击相关的重大安全问题。 本文讲述了我在项目中发现并利用 XXE 漏洞的过程&#xff0c;特别是通过一种非传统的方式——利用 Java 异常在日志文件中输出攻击结果。 什么是XXE&a…

基于STM32的太阳跟踪系统设计

引言 本项目设计了一个基于STM32的太阳跟踪系统&#xff0c;通过光敏传感器阵列实时检测太阳位置&#xff0c;并控制电机驱动太阳能板或光伏板跟随太阳移动&#xff0c;从而最大化太阳能的利用效率。该系统使用双轴运动控制&#xff0c;实现水平和垂直方向的精确跟踪&#xff…

【Java】类型转换与类型提升

目录 1.类型转换 1.1自动类型转换&#xff08;隐式&#xff09; 1.2强制类型转化&#xff08;显式&#xff09; 2.类型提升 3.字符串类型 1.类型转换 Java作为一个强类型编程语言,当不同类型之间的变量相互赋值的时候,会有教严格的校验. 在Java中&#xff0c;当参与运算数…

[单master节点k8s部署]36.ingress 配置https(三)

目前我们的tomcat服务在浏览器上通过http来访问。为了提升安全性&#xff0c;我们将配置TLS secret 证书&#xff0c;从而可以进行https访问。 一对TLS密钥包括一个证书&#xff08;trs.crt&#xff09;和一个私钥&#xff0c;证书是公钥证书&#xff0c;用于加密数据并标识服…

气膜滑冰馆的现实意义:冰雪运动的全民普及—轻空间

气膜滑冰馆的出现不仅是城市发展中的一项基础设施建设&#xff0c;更代表着冰雪运动的逐步普及和全民健身理念的深入人心。在过去&#xff0c;许多地方的冰上运动资源相对匮乏&#xff0c;而如今&#xff0c;气膜滑冰馆通过其独特的优势&#xff0c;弥补了这一空白&#xff0c;…

Fleet Command

边缘计算 文章目录 前言一、边缘创造一个更快速、更智能、联系更紧密的世界二、优势边缘计算的优势1. 降低延迟2. 提高可靠性3. 降低成本4. 更广的覆盖范围三、创新借助 NVIDIA 实现边缘创新1. 企业边缘计算2. 工业边缘 AI3. 机器人和边缘 AI4. 构建面向 AI 时代的应用5. 边缘生…

C++——反向迭代器

1.回顾 template<class T> struct __list_iterator {typedef list_node<T> Node;typedef __list_iterator<T> self;Node* _node;__list_iterator(Node* node):_node(node){}self& operator(){_node _node->_next;return *this;}T& operator*(){…

C# 删除插入-列表排序字典

C# 删除插入-列表排序字典 测试文件 using System; using System.Collections; using System.Collections.Generic;using

五、Linux之Vi和Vim编辑器

基本介绍 Vi Linux 系统会内置 vi 文本编辑 Vim 具有程序编辑的能力&#xff0c;可以看做是 Vi 的增强版本&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。 代码补完、编译及错误跳转等方便编程的功能特别丰富 常用的三种模式 正常模式 以 vim …

如何将 html 渲染后的节点传递给后端?

问题 现在我有一个动态的 html 节点&#xff0c;我想用 vue 渲染后&#xff0c;传递给后端保存 思路 本来想给html的&#xff0c;发现样式是个问题 在一个是打印成pdf&#xff0c;然后上传&#xff0c;这个操作就变多了 最后的思路是通过 html2canvas 转化成 canvas 然后变成…

鸿蒙--WaterFlow 实现商城首页

目录结构 ├──entry/src/main/ets // 代码区 │ ├──common │ │ ├──constants │ │ │ └──CommonConstants.ets // 公共常量类 │ │ └──utils │ │ └──Logger.ets // 日志打印类 │ ├──entryability │ │ └──EntryAbility.ets // 程序入口…

TypeScript 中命名空间与模块的理解及区别

文章目录 一、模块&#xff08;Modules&#xff09;示例 二、命名空间&#xff08;Namespaces&#xff09;示例 三、区别 一、模块&#xff08;Modules&#xff09; 在 TypeScript 中&#xff0c;任何包含顶级 import 或 export 声明的文件都被视为一个模块。模块的特点是它有…

未来的电影:人机环境生态系统智能

本文摘自《影视产业研究》2024年10月创刊号 摘要: 随着人工智能的快速发展&#xff0c;未来的电影得到了广泛关注。通过对未来电影相关研究提供了一种将人机环境系统智能与影游结合的方式来解决未来电影的瓶颈问题&#xff0c;并从态势感知相关研究角度进行了研究。鉴于此&…

阿里云等联合编写的《2024大模型典型示范应用案例集》(附PDF分享)

这份大模型案例集资料已经上传CSDN&#xff0c;朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 2024 年是大模型深入赋能千行百业&#xff0c;融入实体经济&#xff0c;助力科技创新的一年。截至今年5月&#xff0c;我国国产大模型的数量已经超过…

taozige/基于Java语言的充电桩平台+充电桩系统+充电桩管理系统+充电桩系统源码+充电桩管理后台+充电桩小程序

简述 SpringBoot 框架&#xff0c;充电桩平台充电桩系统充电平台充电桩互联互通协议云快充协议1.5新能源汽车电动自行车公交车-四轮车充电充电源代码充电平台源码Java源码无加密项目 介绍 云快充协议云快充1.5协议云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充…

Python人脸识别实战——基于Dlib和OpenCV的人脸识别与关键点检测(附完整代码和结果图)

Python人脸识别实战——基于Dlib和OpenCV的人脸识别与关键点检测&#xff08;附完整代码和结果图&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0…

基于FPGA的以太网设计(一)

以太网简介 以太网&#xff08;Ethernet&#xff09;是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准&#xff0c;它规定了包括物理层的连线、电子信号和介质访问控制的内容。以太网是目前应用最普遍的局域网技术&#xff0c;取代了其他局域网标准如…

在线深度学习:爱奇艺效果广告分钟级模型优化

01# 背景 在效果广告投放场景中&#xff0c;媒体侧需要准确衡量每次请求的价值&#xff0c;模型预估值在广告竞价中扮演着核心角色。模型预估精度的提升&#xff0c;是改善媒体侧变现效率、提升广告收益的核心技术驱动力。 此前&#xff0c;爱奇艺效果广告预估模型为小时级模型…