SH3001六轴传感器应用(二)(IIC驱动开发)

一、前言

我这边使用的开发板原本已经做好了该sensor的驱动,但是使用过程中发现,原始驱动sensor是通过事件的方式上报的,加速度和陀螺仪数据并不同步,不满足使用要求,只有重新写一个iic的驱动,进行sensor数据的读取了。

二、驱动编写

1、linuxIIC驱动属于字符设备的驱动,所以使用字符设备的固定流程进行注册就行了。首先第一步肯定是设备数的编写如下,这里肯定是对应硬件接口IIC子节点下,进行编写,这里的核心是“compatible ”属性,这个一定要和驱动文件的compatible 保持一致,不然无法找到设备节点。

&i2c4 {status = "okay";pinctrl-0 = <&i2c4m1_xfer>;sh3001_acc@36 {compatible = "sh3001";reg = <0x36>;type = <SENSOR_TYPE_ACCEL>;pinctrl-names = "default";pinctrl-0 = <&sh3001_irq_gpio>;irq-gpio = <&gpio2 RK_PC4 IRQ_TYPE_LEVEL_HIGH>;irq_enable = <0>;poll_delay_ms = <30>;layout = <2>;status = "okay";};
}

2、接下来就是设备的init 和exit了,init用于开始进行设备的匹配,exit用于在驱动设备卸载时执行。

static int __init sh3001_init(void)
{int ret = 0;ret = i2c_add_driver(&sh3001_driver);return ret;
}static void __exit sh3001_exit(void)
{i2c_del_driver(&sh3001_driver);
}module_init(sh3001_init);
module_exit(sh3001_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TRACY");

3、在执行module_init(sh3001_init)的时候,会调用sh3001_driver结构体,这里将会将会进行设备的匹配,sh3001_of_match的结构体数组就是去匹配设备数相关节点的。


/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {{"sh3001", 0},  {}
};/*设备数匹配列表*/
static const struct of_device_id sh3001_of_match[] = {{.compatible = "sh3001"},{/*Sentinel*/}
};/*sh3001驱动结构体 */
static struct i2c_driver sh3001_driver = {.probe = sh3001_probe,.remove =sh3001_remove,.driver = {.owner = THIS_MODULE,.name = "sh3001",.of_match_table = sh3001_of_match},.id_table = ap3216c_id};

这里在说明一下.id_table = ap3216c_id 和设备树 of_match_table 之间的关系。 

.id_table 是用于 传统非设备树匹配(非-DT)平台 上的 I2C 设备名称匹配。它提供了一个 字符串设备名数组(如 "sh3001"),供内核在 手动注册 i2c_client 的平台 中与驱动进行匹配(例如老的板文件、用户空间调用 i2c_new_device())。

和设备树 of_match_table 的区别?

匹配方式使用场景依据需要的字段
.id_table非设备树平台i2c_client->name.id_table = ...
.of_match_table设备树平台(Device Tree)compatible.of_match_table = ...

也就是说:

  • 设备树驱动匹配走的是 of_match_table(即你设备树中写了 compatible = "xxx"

  • 传统方式(不使用设备树)走的是 id_table

  • 内核 会将匹配的 of_device_id 转换为 i2c_device_id(如果 .id_table 存在)传给 probe 的 id 参数。

  • 你用的是设备树,如果 id_table 不写或者为 NULL,内核也可以匹配,但有些框架依赖 id_table 比如 i2c_register_driver() 的自动注册机制,或者 module alias 功能就会缺失。

static const struct i2c_device_id ap3216c_id[] = {{ "ap3216c", 0 },{ }
};
MODULE_DEVICE_TABLE(i2c, ap3216c_id);  // 重要!static const struct of_device_id ap3216c_of_match[] = {{ .compatible = "liteon,ap3216c" },{ }
};
MODULE_DEVICE_TABLE(of, ap3216c_of_match);

这样你的驱动即支持设备树匹配,又支持非设备树注册,同时也能被 modprobeudev 正确识别。

4、设备数成功匹配后,就开始probe()函数的调用,首先得定义定义一个标准的IIC设备的结构体。

接下也算是固定流程:

分配结构体内存---->注册字符设备、创建设备号----->初始化字符设备----->添加字符设备----->创建类----->创建设备。

这里需要注意的就是需要确定这些创建、添加是否成功。

struct sh3001_dev{struct i2c_client * client;   /*i2c 设备*/dev_t   devid;                  /*设备号 */struct cdev  cdev;              /*字符设备核心*/struct class * class;            /*类*/struct device * device;         /*设备*/struct device_node * nd;       /*设备节点*/};/** @description 	: i2c驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param – client	: i2c设备* @param - id      	: i2c设备ID* @return          	: 0,成功;其他负值,失败*/
static int sh3001_probe(struct i2c_client * client,const struct i2c_device_id *id)
{int ret;struct sh3001_dev * sh3001_cdev;/*分配结构体内存*/sh3001_cdev=devm_kzalloc(&client->dev,sizeof(*sh3001_cdev),GFP_KERNEL);if(!sh3001_cdev){return -ENOMEM;}/*注册字符设备驱动*//*创建设备号*/ret = alloc_chrdev_region(&sh3001_cdev->devid,0,SH3001_CNT,SH3001_NAME);if(ret<0){pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", SH3001_NAME, ret);return -ENOMEM;}else{printk("alloc chrdev sucess\n");}/*初始化cdev*/sh3001_cdev->cdev.owner = THIS_MODULE;cdev_init(&sh3001_cdev->cdev,&sh3001_ops);/*添加一个cdev*/ret = cdev_add(&sh3001_cdev->cdev,sh3001_cdev->devid,SH3001_CNT);if(ret < 0){goto del_unregister;}else{printk("cdev add sucess cdev\n");}/*创建类 */sh3001_cdev->class = class_create(THIS_MODULE,SH3001_NAME);if(IS_ERR(sh3001_cdev->class))goto del_cdev;printk("create class sucess\n");/*创建设备*/sh3001_cdev->device = device_create(sh3001_cdev->class,NULL,sh3001_cdev->devid,NULL,SH3001_NAME);if(IS_ERR(sh3001_cdev->device))goto destroy_class;sh3001_cdev->client=client;i2c_set_clientdata(client,sh3001_cdev);printk("create device sucess:%p  iic_client:%p\n",(void *)&sh3001_cdev->cdev,(void *)sh3001_cdev->client);return 0;del_cdev:cdev_del(&sh3001_cdev->cdev);destroy_class:device_destroy(sh3001_cdev->class,sh3001_cdev->devid);del_unregister:unregister_chrdev_region(sh3001_cdev->devid, SH3001_CNT);return -EIO;}

5、上面probe()在初始化字符设备时是映射了应用的接口的。read()write()的方式比较常见,这里我们使用ioctl去进行寄存器的控制读取。

 static const struct file_operations sh3001_ops = {.owner = THIS_MODULE,.open = sh3001_open,.unlocked_ioctl = sh3001_ioctl,.compat_ioctl = sh3001_ioctl,.release = sh3001_release
};cdev_init(&sh3001_cdev->cdev,&sh3001_ops);

使用ioctl需要注意的就是命令的注册

#define  SH3001_IOCTL_WRITE    _IOW('s',0x01,struct sh3001_ioctl_data)
#define  SH3001_IOCTL_READ     _IOR('s',0x02,struct sh3001_ioctl_data)
#define  SH3001_IOCTL_START    _IOR('s',0x03,struct sh3001_ioctl_data)

这些宏是 Linux 提供的,用来创建 ioctl 命令编号。其目的是:

唯一标识一个 ioctl 命令,并且告诉内核和驱动:

  • 命令属于哪个设备

  • 命令编号是多少

  • 用户是否传递了参数

  • 参数的方向(从用户传给内核,还是从内核返回给用户)

  • 参数的数据结构是什么

  •  并且这个命令在应用层也有一样使用调用,不然应用层无法传递到对应ioctl的接口。

接下来就是ioctl代码的实现,其核心就是通过文件指针找到iic的驱动地址。

static long sh3001_ioctl (struct file * filp, unsigned int cmd, unsigned long arg_addr)
{//printk("enter ioctl\n");#if 1struct sh3001_ioctl_data data;int i;short acc_x,acc_y,acc_z,gyr_x,gyr_y,gyr_z;/* 从file结构体获取cdev指针,再根据cdev获取sh3001_dev首地址 */struct cdev * cdev = filp->f_path.dentry->d_inode->i_cdev;struct sh3001_dev * sh3001cdev=container_of(cdev,struct sh3001_dev,cdev);pr_info("sh3001_ioctl called with cmd=0x%x\n", cmd);/*返回 0 表示所有 n 字节数据成功拷贝返回 >0 的正数 表示拷贝失败的字节数(即实际成功拷贝了 n - ret 字节)*/if(copy_from_user(&data,(void __user *)arg_addr,sizeof(data))){dev_err(sh3001cdev->device, "copy user data error\n");return -EFAULT;}if(!(sh3001cdev->client)){dev_err(sh3001cdev->device, "no iic client\n");return -ENODEV;}switch(cmd){case SH3001_IOCTL_WRITE:break;case SH3001_IOCTL_READ:sh3001_read_regs(sh3001cdev->client,data.reg,12,data.val);printk("ioctl reg:%d  data:\n",data.reg);for(i=0;i<6;i++){if(i%2==0)printk(" ");printk("%02x%02x",data.val[2*i+1],data.val[2*i]);}if(copy_to_user((void __user *)arg_addr,&data,sizeof(data))){return -EFAULT;}break;case SH3001_IOCTL_START:for(i=0;i<200;i++){sh3001_read_regs(sh3001cdev->client,data.reg,12,data.val);acc_x += ((data.val[1] << 8) & 0xFF00) + (data.val[0] & 0xFF);acc_y += ((data.val[3] << 8) & 0xFF00) + (data.val[2] & 0xFF);acc_z += ((data.val[5] << 8) & 0xFF00) + (data.val[4] & 0xFF);gyr_x += ((data.val[1] << 8) & 0xFF00) + (data.val[0] & 0xFF);gyr_y += ((data.val[3] << 8) & 0xFF00) + (data.val[2] & 0xFF);gyr_z += ((data.val[5] << 8) & 0xFF00) + (data.val[4] & 0xFF);msleep(5);}acc_x = acc_x/200;acc_y = acc_y/200;acc_z = acc_z/200;gyr_x = gyr_x/200;gyr_y = gyr_y/200;gyr_z = gyr_z/200;memcpy(&data.val[0],&acc_x,2);memcpy(&data.val[2],&acc_y,2);memcpy(&data.val[4],&acc_z,2);memcpy(&data.val[6],&gyr_x,2);memcpy(&data.val[8],&gyr_y,2);memcpy(&data.val[10],&gyr_z,2);if(copy_to_user((void __user *)arg_addr,&data,sizeof(data))){return -EFAULT;}break;default:break;}#endifreturn 0;
}

三、编译 

 1、接下来就是将驱动文件.c编译成ko模块,makefile如下:

obj-m += sh3001_driver.oKDIR := /home/tracy/rockchip/linux_sdk/kernel  # kernel source directory
PWD := $(shell pwd)    #current directoryall:make -C $(KDIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-buildroot-linux-gnu- modulesclean:make -C $(KDIR) M=$(PWD) clean

 2、将编译好的驱动文件加载进驱动中可以使用insmod和modprobe命令,推荐使用modprobe命令,insmod 会自动处理依赖和参数传递。

rmmod移出驱动命令。

lsmod列出加载驱动命令。

四、调试技巧

在调试过程可以使用示波器查看接口是否有波形产生,多使用printk()打印调试日志。使用dmesg命令进行查看。

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

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

相关文章

面试题:基础的sql命令

基础的 SQL 命令主要用于对数据库进行查询、新增、修改、删除等操作&#xff0c;可分为以下几类&#xff1a;一、数据查询&#xff08;SELECT&#xff09;用于从表中获取数据&#xff0c;是最常用的命令。 基本语法&#xff1a;SELECT 列名1, 列名2... FROM 表名 WHERE 条件;示…

Leetcode-3488距离最小相等元素查询

依旧二分&#xff0c;链接如下3488. 距离最小相等元素查询 看题目是个循环数组&#xff0c;记得当时做过一道什么题也是循环数组&#xff0c;就想着直接数组复制一下&#xff0c;然后跟上一道题一样&#xff0c;用hashmap来存储value的值以及value对应下标的vector。 和灵神的…

C++中的关联容器

文章目录使用关联容器定义关联容器关键字类型的要求pair类型用作返回类型关联容器上的操作关联容器的迭代器关联容器和算法添加元素删除元素map的下标操作访问元素无序容器对关键字的要求关联容器支持高效的关键字查找和访问。两个主要的关联容器的类型是map和set。其中map中的…

【Git】git提交代码报错Git: husky > pre-commit

git提交代码报错原因 这个问题是因为当你在终端输入git commit -m “XXX”,提交代码的时候,pre-commit(客户端)钩子&#xff0c;它会在Git键入提交信息前运行做代码风格检查。如果代码不符合相应规则&#xff0c;则报错&#xff0c;而它的检测规则就是根据.git/hooks/pre-commi…

Unity开发者快速认识Unreal 的C++(六)GameMode之PlayerController

继承关系&#xff1a;Aactor&#xff0c;INavAgentInterface <--- AController<--- PlayerController &#xff0c;PlayerController也是一个Actor,继承了Actor的一些通用的属性和工具函数下图是PlayerController初始化组件的一个子阶段从图中可以得到的信息是&#xf…

Vue 3 服务端渲染(SSR)与客户端渲染(CSR)的区别及解决方案

1. SSR与CSR的区别1.1. SSR的原理服务端渲染&#xff08;SSR&#xff09;是在服务器端将 Vue 组件渲染为 HTML 字符串&#xff0c;并将其发送给客户端。这种方式与客户端渲染&#xff08;CSR&#xff09;不同&#xff0c;后者是在浏览器中执行 JavaScript 来生成 HTML。在 SSR …

Matlab快速回顾

一1.数值 显示 格式format style 设置eg: pi format longE;or2.清除指令clc 清除命令行窗口clear 清除工作区cls3.搜索路径设置path(path,E:\ads\)oraddpath4.M文件用户把要实现的命令写在一个以.m为扩展的文件中&#xff0c;然后由matlab系统进行解读&#xff0c;最后运行结果…

开源低代码+AI引擎:百特搭企业级开发平台的演进

在数字化转型进入深水区的今天&#xff0c;企业应用开发面临前所未有的复杂挑战&#xff1a;既要快速响应业务需求&#xff0c;又要确保系统灵活可控&#xff1b;既要降低技术门槛&#xff0c;又要保障核心安全。传统开发模式与单一形态的低代码工具已难以满足多层次需求。融合…

学习 Android(十五)NDK进阶及性能优化

学习 Android&#xff08;十五&#xff09;NDK进阶及性能优化 对 NDK 相关知识有了初步的了解之后&#xff0c;我们可以更加深入的去学习 NDK 相关知识&#xff0c;接下来&#xff0c;我们将按照以下步骤进行深入学习&#xff1a; 深入理解JNI调用过程和性能消耗常见 JNI 坑&am…

QT5.12.8 QTabWidget 透明样式QSS

/* 设置QTabWidget本身 :不加也行*/ QTabWidget#aaa_tabwdt {background: transparent;border: none; /* 移除边框可能有助于透明效果 */ }/* 标签页内的容器部件 :必须加&#xff0c;标签也才会透明 */ QTabWidget#aaa_tabwdt QWidget, QTabWidget#aaa_tabwdt QFrame {backgro…

【FAQ】Script导出SharePoint 目录文件列表并统计大小

一、只导出文件列表的方法 1) 保存脚本&#xff08;建议名&#xff1a;D:\tmp\Export-SharePoint-FileList.ps1&#xff09; <# 导出 SharePoint 指定文件夹&#xff08;含子文件夹&#xff09;的文件列表到 CSV&#xff08;不统计大小&#xff09; 前提&#xff1a;已安…

《Thinking in Java》读书笔记---控制执行流程

就像有感知的生物一样&#xff0c;程序必须在执行过程中控制它的世界&#xff0c;并做出选择。在Java中&#xff0c;你要使用执行控制语句来作出选择。一、流程控制基础概念1.1 流程控制的重要性流程控制结构决定了程序执行的顺序和逻辑分支&#xff0c;是编程语言中最基础也是…

极验 G-star 人才特训营:为业务安全领域培养下一代新兴力量

本文导读 极验为什么要启动 G-star 实习生培养计划&#xff1f;50多位来自多所高校的同学&#xff0c;在极验经历了一场怎样的“非典型”实习&#xff1f;技术大咖亲授&#xff0c;先培训再实战&#xff0c;极验打造的是怎样的人才体系&#xff1f;同学有话说&#xff1a;培养计…

攻防世界-web-csaw-mfw

一.题目分析这边提示使用了Git&#xff0c;试着访问.git看是否存在.git泄露浏览了一下&#xff0c;很多都是乱码&#xff0c;想着用githack将git库克隆下看一下二.操作python2 GitHack.py http://url/.git访问了一下flag.php&#xff0c;没啥发现&#xff0c;在看一下index.php…

202506 电子学会青少年等级考试机器人四级实际操作真题

更多内容和历年真题请查看网站&#xff1a;【试卷中心 -----> 电子学会 ----> 机器人技术 ----> 四级】 网站链接 青少年软件编程历年真题模拟题实时更新 2025年6月 青少年等级考试机器人实操真题四级 实际操作 主题&#xff1a;感应节能灯&#xff08;四级&am…

DLT645电表数据 保存到MySQL数据库项目案例

目录 1 案例说明 2 VFBOX网关工作原理 3 准备工作 4 配置VFBOX网关采集DLT645电表数据 5 网关写数据到MYSQL数据库 6 安装MYSQL数据库 7 其他说明 8 案例总结 1 案例说明 设置网关采集DLT645电表数据数据把采集的数据保存到MySQL数据库。 2 VFBOX网关工作原理 VFBOX网关…

Redux与React - 异步状态操作(React快速上手4)

异步操作样板代码1. 创建store的写法保持不变&#xff0c;配置好同步修改状态的方法 2. 单独封装一个函数&#xff0c;在函数内部return一个新函数&#xff0c;在新函数中 2.1 封装异步请求获取数据 2.2 调用同步actionCreater传入异步数据生成一个action对象&#xff0c;并使用…

win10桌面右键没有新建word

win10右键新建word不见解决方法1、点击开始&#xff0c;找到运行命令行&#xff0c;输入regedit&#xff0c;打开注册表。2、在左侧找到HKEY_CLASSES_ROOT目录&#xff0c;并展开。3.找到.docx 双击&#xff08;默认&#xff09;一项&#xff0c;将其改为 Word.Document.12。关…

Docker国内可用镜像(2025.08.06测试)

Docker渡渡鸟镜像可用&#xff08;测试时间2025.08.06&#xff09;https://docker.aityp.com/使用渡渡鸟镜像pull ollama latest 例子&#xff1a;docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/ollama/ollama:0.10.1毫秒镜像和轩辕镜像也可用&#xff0c;但…

决策树的实际案例

决策树作为一种直观、易解释的机器学习算法&#xff0c;在金融、医疗、电商、风控等多个领域都有广泛的实际应用。以下将讲解1个典型案例&#xff1a;贷款违约预测。对于贷款违约预测&#xff0c;即在贷款人员在贷款之前对其进行预测&#xff0c;通过他的众多特征情况判别是否可…