SH3001六轴传感器应用(二)(IIC驱动开发)_STM32驱动SH3001传感器
一、前言
我这边使用的开发板原本已经做好了该sensor的驱动,但是使用过程中发现,原始驱动sensor是通过事件的方式上报的,加速度和陀螺仪数据并不同步,不满足使用要求,只有重新写一个iic的驱动,进行sensor数据的读取了。
二、驱动编写
1、linuxIIC驱动属于字符设备的驱动,所以使用字符设备的固定流程进行注册就行了。首先第一步肯定是设备数的编写如下,这里肯定是对应硬件接口IIC子节点下,进行编写,这里的核心是“compatible ”属性,这个一定要和驱动文件的compatible 保持一致,不然无法找到设备节点。
&i2c4 {status = \"okay\";pinctrl-0 = ;sh3001_acc@36 {compatible = \"sh3001\";reg = ;type = ;pinctrl-names = \"default\";pinctrl-0 = ;irq-gpio = ;irq_enable = ;poll_delay_ms = ;layout = ;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
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);
这样你的驱动即支持设备树匹配,又支持非设备树注册,同时也能被 modprobe
和 udev
正确识别。
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(retcdev.owner = THIS_MODULE; cdev_init(&sh3001_cdev->cdev,&sh3001_ops); /*添加一个cdev*/ ret = cdev_add(&sh3001_cdev->cdev,sh3001_cdev->devid,SH3001_CNT); if(ret 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 1 struct 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;iclient,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; }#endif return 0;}
三、编译
1、接下来就是将驱动文件.c编译成ko模块,makefile如下:
obj-m += sh3001_driver.oKDIR := /home/tracy/rockchip/linux_sdk/kernel # kernel source directoryPWD := $(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命令进行查看。