字符设备驱动编写

文章目录

    • 环境
    • 一、添加驱动(/sys/bus/i2c/drivers/mpu6050_1)
    • 驱动和设备树扯上关系
    • 二、注册一个(种/类?)字符设备(/proc/devices,243 mpu6050_1)
    • 三、手动创建一个字符设备(mknod /dev/mpu6050 c 243 0)
    • 提供操作函数
    • 四、类文件(/sys/class)
    • 五、自动创建设备文件(/dev/mpu6050_3)
    • 总结

环境

树莓派 3b+
mpu6050 模块
插在物理 3、5 引脚
在这里插入图片描述

一、添加驱动(/sys/bus/i2c/drivers/mpu6050_1)

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>struct i2c_driver mpu6050_driver = {// .probe = mpu6050_probe,// .remove = mpu6050_remove,.driver = {.name = "mpu6050_1",// .of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");

Makefile

obj-m = mpu6050.oKDIR=/home/liyongjun/project/board/buildroot/RPi3/build/linux-custom
CROSS_COMPILE=/home/liyongjun/project/board/buildroot/RPi3/host/bin/arm-buildroot-linux-gnueabihf-all:make -C ${KDIR} M=${PWD} ARCH=arm CROSS_COMPILE=${CROSS_COMPILE} modulesclean:make -C ${KDIR} M=${PWD} ARCH=arm CROSS_COMPILE=${CROSS_COMPILE} clean

使用 i2c_add_driver() 向 i2c 总线上添加一个驱动,这个驱动没有任何功能,只有一个驱动名 “mpu6050_1”。
可以在 /sys/bus/i2c/drivers 目录下查看到该驱动。

# ls /sys/bus/i2c/drivers
dummy      stmpe-i2c
# 
# insmod mpu6050.ko 
#
# ls /sys/bus/i2c/drivers
dummy      mpu6050_1  stmpe-i2c
#
# ls /sys/bus/i2c/drivers/mpu6050_1/
bind    module  uevent  unbind

驱动和设备树扯上关系

arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dts

&i2c1 {pinctrl-names = "default";pinctrl-0 = <&i2c1_pins>;clock-frequency = <100000>;status = "okay";mpu6050: i2cdev@68 {compatible = "lyj,mpu6050";reg = <0x68>;status = "okay";};
};

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mpu6050_probe()\n");printk("flags = 0x%x\n", client->flags);printk("addr = 0x%x\n", client->addr);printk("name = %s\n", client->name);printk("adapter name = %s\n", client->adapter->name);printk("dev init_name = %s\n", client->dev.init_name);return 0;
}static int mpu6050_remove(struct i2c_client *client)
{printk("mpu6050_remove()\n");return 0;
}/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{.compatible = "lyj,mpu6050"},{},
};struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.name = "mpu6050_1",.of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");

insmod ko 后,执行 i2c_add_driver(&mpu6050_driver); 向 i2c 总线上注册驱动,内核 i2c 总线会遍历总线上的设备,试图找到和该驱动匹配的未被驱动的设备。
结果就找到了 i2c1 下的 mpu6050: i2cdev@68 ,该设备是在内核解析设备树时添加到 i2c 总线上的。
该驱动支持的列表 .compatible = "lyj,mpu6050" 和设备树的 compatible = "lyj,mpu6050"; 信息匹配,则调用该驱动的 probe() 函数,开始驱动该设备。
并且,内核会将设备信息传递到 probe() 函数的参数中。比如,设备地址为 0x68,设备名称为 mpu6050,设备所属的 i2c 控制器为 i2c@7e804000。

# insmod mpu6050.ko 
[ 4901.348057] mpu6050_probe()
[ 4901.352230] flags = 0x0
[ 4901.356057] addr = 0x68
[ 4901.359835] name = mpu6050
[ 4901.363850] adapter name = bcm2835 (i2c@7e804000)
[ 4901.369882] dev init_name = (null)

rmmod 时,会执行 remove() 函数

# rmmod mpu6050
[ 6416.835182] mpu6050_remove()

二、注册一个(种/类?)字符设备(/proc/devices,243 mpu6050_1)

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>static int major;static const struct file_operations mpu6050_fops = {// .open =		mpu6050_dev_open,// .read =		mpu6050_dev_read,
};int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mpu6050_probe()\n");printk("flags = 0x%x\n", client->flags);printk("addr = 0x%x\n", client->addr);printk("name = %s\n", client->name);printk("adapter name = %s\n", client->adapter->name);printk("dev init_name = %s\n", client->dev.init_name);major = register_chrdev(0, "mpu6050_1", &mpu6050_fops); // 注册字符设备return 0;
}static int mpu6050_remove(struct i2c_client *client)
{printk("mpu6050_remove()\n");unregister_chrdev(major, "mpu6050_1");  // 取消注册字符设备return 0;
}/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{.compatible = "lyj,mpu6050"},{},
};struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.name = "mpu6050_1",.of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");
# insmod mpu6050.ko 
[ 6602.199592] mpu6050_probe()
[ 6602.203828] flags = 0x0
[ 6602.207592] addr = 0x68
[ 6602.211352] name = mpu6050
[ 6602.215408] adapter name = bcm2835 (i2c@7e804000)
[ 6602.221470] dev init_name = (null)
# cat /proc/devices | grep mpu6050
243 mpu6050_1

三、手动创建一个字符设备(mknod /dev/mpu6050 c 243 0)

# mknod /dev/mpu6050 c 243 0
#
# ls /dev/mpu6050 -lh
crw-r--r--    1 root     root      243,   0 Jan  1 02:12 /dev/mpu6050

之后,就可以对该设备进行 open()、read()、write()、ioctl()、close() 等操作了,前提是得先注册好上述操作函数。
上面的示例中,我们还没有提供操作函数,如果这个时候去读写设备文件,将会发生错误。
接下来,我们完善操作函数。

提供操作函数

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include "mpu6050.h"struct i2c_client *mpu6050_client;
static int major;static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];/* 先写 */mpu6050_msg[0].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[0].flags = 0;					// 写指令mpu6050_msg[0].buf = &address_data;			// 写入的数据mpu6050_msg[0].len = 1;						// 数据长度/* 再读 */mpu6050_msg[1].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[1].flags = I2C_M_RD;			// 读指令mpu6050_msg[1].buf = data;					// 读取得到的数据保存位置mpu6050_msg[1].len = length;				// 读取长度error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2) {printk("i2c_read_mpu6050 error\n");return -1;}return 0;
}ssize_t mpu6050_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{char data_H;char data_L;int error;short mpu6050_result[6]; // 保存 mpu6050 转换得到的原始数据// printk("\n mpu6050_read \n");i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);mpu6050_result[0] = data_H << 8;mpu6050_result[0] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);mpu6050_result[1] = data_H << 8;mpu6050_result[1] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);mpu6050_result[2] = data_H << 8;mpu6050_result[2] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);mpu6050_result[3] = data_H << 8;mpu6050_result[3] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);mpu6050_result[4] = data_H << 8;mpu6050_result[4] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);mpu6050_result[5] = data_H << 8;mpu6050_result[5] += data_L;// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);/* 将读取得到的数据拷贝到用户空间 */error = copy_to_user(buf, mpu6050_result, cnt);if (error != 0) {printk("copy_to_user error!");return -1;}return 0;
}static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{int error = 0;u8 write_data[2];struct i2c_msg send_msg; // 要写入的数据结构体/* 设置要写入的数据 */write_data[0] = address; // 寄存器地址write_data[1] = data;    // 数据send_msg.addr = mpu6050_client->addr; // mpu6050 设备地址send_msg.flags = 0;         // 写指令send_msg.buf = write_data;  // 写入的数据send_msg.len = 2;           // 数据长度/* 执行写入 */error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1) {printk("i2c_transfer error\n");return -1;}return 0;
}static int mpu6050_init(void)
{int error = 0;/* 配置 mpu6050 */error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);if (error < 0) {/* 初始化失败 */printk("mpu6050_init error\n");return -1;}return 0;
}int mpu6050_dev_open(struct inode *, struct file *)
{printk("mpu6050_dev_open()\n");/* 向 mpu6050 发送配置数据,让 mpu6050 处于正常工作状态 */mpu6050_init();return 0;
}static const struct file_operations mpu6050_fops = {.open = mpu6050_dev_open,.read = mpu6050_dev_read,
};int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mpu6050_probe()\n");printk("flags = 0x%x\n", client->flags);printk("addr = 0x%x\n", client->addr);printk("name = %s\n", client->name);printk("adapter name = %s\n", client->adapter->name);printk("dev init_name = %s\n", client->dev.init_name);major = register_chrdev(0, "mpu6050_1", &mpu6050_fops); // 注册字符设备mpu6050_client = client;return 0;
}static int mpu6050_remove(struct i2c_client *client)
{printk("mpu6050_remove()\n");unregister_chrdev(major, "mpu6050_1"); // 取消注册字符设备return 0;
}/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{ .compatible = "lyj,mpu6050" },{},
};struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.name = "mpu6050_1",.of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");

test_app.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int error;short resive_data[6]; // 保存收到的 mpu6050 转换结果数据, 依次为 AX(x轴角度), AY, AZ; GX(x轴加速度), GY, GZint fd = open(argv[1], O_RDWR);if (fd < 0) {printf("open file : %s failed !\n", argv[0]);return -1;}/*读取数据*/while (1) {error = read(fd, resive_data, 12);if (error < 0) {printf("read file error! \n");} else {printf("AX = %6d, AY = %6d, AZ = %6d", (int)resive_data[0], (int)resive_data[1], (int)resive_data[2]);printf("\t\tGX = %6d, GY = %6d, GZ = %6d\n", (int)resive_data[3], (int)resive_data[4], (int)resive_data[5]);}sleep(1);}close(fd);return 0;
}

Makefile

obj-m = mpu6050.oKDIR=/home/liyongjun/project/board/buildroot/RPi3/build/linux-custom
CROSS_COMPILE=/home/liyongjun/project/board/buildroot/RPi3/host/bin/arm-buildroot-linux-gnueabihf-
CC=${CROSS_COMPILE}gccall:make -C ${KDIR} M=${PWD} ARCH=arm CROSS_COMPILE=${CROSS_COMPILE} modulestest:${CC} test_app.c -o test_app.out -Wallclean:make -C ${KDIR} M=${PWD} ARCH=arm CROSS_COMPILE=${CROSS_COMPILE} clean
# ./test_app.out /dev/mpu6050 
[ 8909.123645] mpu6050_dev_open()
AX = -12966, AY =   9260, AZ =  -7862		GX =  -1282, GY =     40, GZ =    193
AX = -12948, AY =   9264, AZ =  -7864		GX =  -1284, GY =     39, GZ =    193
AX = -12950, AY =   9284, AZ =  -7850		GX =  -1282, GY =     40, GZ =    192

四、类文件(/sys/class)

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include "mpu6050.h"struct i2c_client *mpu6050_client;
static int major;
struct class *class_mpu6050;static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];/* 先写 */mpu6050_msg[0].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[0].flags = 0; // 写指令mpu6050_msg[0].buf = &address_data; // 写入的数据mpu6050_msg[0].len = 1; // 数据长度/* 再读 */mpu6050_msg[1].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[1].flags = I2C_M_RD; // 读指令mpu6050_msg[1].buf = data; // 读取得到的数据保存位置mpu6050_msg[1].len = length; // 读取长度error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2) {printk("i2c_read_mpu6050 error\n");return -1;}return 0;
}ssize_t mpu6050_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{char data_H;char data_L;int error;short mpu6050_result[6]; // 保存 mpu6050 转换得到的原始数据// printk("\n mpu6050_read \n");i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);mpu6050_result[0] = data_H << 8;mpu6050_result[0] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);mpu6050_result[1] = data_H << 8;mpu6050_result[1] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);mpu6050_result[2] = data_H << 8;mpu6050_result[2] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);mpu6050_result[3] = data_H << 8;mpu6050_result[3] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);mpu6050_result[4] = data_H << 8;mpu6050_result[4] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);mpu6050_result[5] = data_H << 8;mpu6050_result[5] += data_L;// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);/* 将读取得到的数据拷贝到用户空间 */error = copy_to_user(buf, mpu6050_result, cnt);if (error != 0) {printk("copy_to_user error!");return -1;}return 0;
}static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{int error = 0;u8 write_data[2];struct i2c_msg send_msg; // 要写入的数据结构体/* 设置要写入的数据 */write_data[0] = address; // 寄存器地址write_data[1] = data; // 数据send_msg.addr = mpu6050_client->addr; // mpu6050 设备地址send_msg.flags = 0; // 写指令send_msg.buf = write_data; // 写入的数据send_msg.len = 2; // 数据长度/* 执行写入 */error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1) {printk("i2c_transfer error\n");return -1;}return 0;
}static int mpu6050_init(void)
{int error = 0;/* 配置 mpu6050 */error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);if (error < 0) {/* 初始化失败 */printk("mpu6050_init error\n");return -1;}return 0;
}int mpu6050_dev_open(struct inode *, struct file *)
{printk("mpu6050_dev_open()\n");/* 向 mpu6050 发送配置数据,让 mpu6050 处于正常工作状态 */mpu6050_init();return 0;
}static const struct file_operations mpu6050_fops = {.open = mpu6050_dev_open,.read = mpu6050_dev_read,
};int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mpu6050_probe()\n");printk("flags = 0x%x\n", client->flags);printk("addr = 0x%x\n", client->addr);printk("name = %s\n", client->name);printk("adapter name = %s\n", client->adapter->name);printk("dev init_name = %s\n", client->dev.init_name);major = register_chrdev(0, "mpu6050_1", &mpu6050_fops); // 注册字符设备class_mpu6050 = class_create(THIS_MODULE, "mpu6050_2"); // 创建类mpu6050_client = client;return 0;
}static int mpu6050_remove(struct i2c_client *client)
{printk("mpu6050_remove()\n");unregister_chrdev(major, "mpu6050_1"); // 取消注册字符设备class_destroy(class_mpu6050); // 删除类return 0;
}/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{ .compatible = "lyj,mpu6050" },{},
};struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.name = "mpu6050_1",.of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");
# ls /sys/class/mpu6050_2/

五、自动创建设备文件(/dev/mpu6050_3)

mpu6050.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include "mpu6050.h"struct i2c_client *mpu6050_client;
static int major;
struct class *class_mpu6050;static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];/* 先写 */mpu6050_msg[0].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[0].flags = 0; // 写指令mpu6050_msg[0].buf = &address_data; // 写入的数据mpu6050_msg[0].len = 1; // 数据长度/* 再读 */mpu6050_msg[1].addr = mpu6050_client->addr; // mpu6050 设备地址mpu6050_msg[1].flags = I2C_M_RD; // 读指令mpu6050_msg[1].buf = data; // 读取得到的数据保存位置mpu6050_msg[1].len = length; // 读取长度error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2) {printk("i2c_read_mpu6050 error\n");return -1;}return 0;
}ssize_t mpu6050_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{char data_H;char data_L;int error;short mpu6050_result[6]; // 保存 mpu6050 转换得到的原始数据// printk("\n mpu6050_read \n");i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);mpu6050_result[0] = data_H << 8;mpu6050_result[0] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);mpu6050_result[1] = data_H << 8;mpu6050_result[1] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);mpu6050_result[2] = data_H << 8;mpu6050_result[2] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);mpu6050_result[3] = data_H << 8;mpu6050_result[3] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);mpu6050_result[4] = data_H << 8;mpu6050_result[4] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);mpu6050_result[5] = data_H << 8;mpu6050_result[5] += data_L;// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);/* 将读取得到的数据拷贝到用户空间 */error = copy_to_user(buf, mpu6050_result, cnt);if (error != 0) {printk("copy_to_user error!");return -1;}return 0;
}static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{int error = 0;u8 write_data[2];struct i2c_msg send_msg; // 要写入的数据结构体/* 设置要写入的数据 */write_data[0] = address; // 寄存器地址write_data[1] = data; // 数据send_msg.addr = mpu6050_client->addr; // mpu6050 设备地址send_msg.flags = 0; // 写指令send_msg.buf = write_data; // 写入的数据send_msg.len = 2; // 数据长度/* 执行写入 */error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1) {printk("i2c_transfer error\n");return -1;}return 0;
}static int mpu6050_init(void)
{int error = 0;/* 配置 mpu6050 */error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);if (error < 0) {/* 初始化失败 */printk("mpu6050_init error\n");return -1;}return 0;
}int mpu6050_dev_open(struct inode *, struct file *)
{printk("mpu6050_dev_open()\n");/* 向 mpu6050 发送配置数据,让 mpu6050 处于正常工作状态 */mpu6050_init();return 0;
}static const struct file_operations mpu6050_fops = {.open = mpu6050_dev_open,.read = mpu6050_dev_read,
};int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{printk("mpu6050_probe()\n");printk("flags = 0x%x\n", client->flags);printk("addr = 0x%x\n", client->addr);printk("name = %s\n", client->name);printk("adapter name = %s\n", client->adapter->name);printk("dev init_name = %s\n", client->dev.init_name);major = register_chrdev(0, "mpu6050_1", &mpu6050_fops); // 注册字符设备class_mpu6050 = class_create(THIS_MODULE, "mpu6050_2"); // 创建类device_create(class_mpu6050, NULL, MKDEV(major, 0), NULL, "mpu6050_3"); // 创建设备 /dev/mpu6050_3mpu6050_client = client;return 0;
}static int mpu6050_remove(struct i2c_client *client)
{printk("mpu6050_remove()\n");unregister_chrdev(major, "mpu6050_1"); // 取消注册字符设备class_destroy(class_mpu6050); // 删除类device_destroy(class_mpu6050, MKDEV(major, 0)); // 删除设备 /dev/mpu6050_3return 0;
}/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{ .compatible = "lyj,mpu6050" },{},
};struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.driver = {.name = "mpu6050_1",.of_match_table = mpu6050_of_match_table,},};static int __init mpu6050_driver_init(void)
{i2c_add_driver(&mpu6050_driver);return 0;
}static void __exit mpu6050_driver_exit(void)
{i2c_del_driver(&mpu6050_driver);return;
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");
# ls /dev/mpu6050_3 -lh
crw-------    1 root     root      243,   0 Jan  1 02:47 /dev/mpu6050_3
#
# ./test_app.out /dev/mpu6050_3 
[10287.394410] mpu6050_dev_open()
AX = -12956, AY =   9256, AZ =  -7830		GX =  -1281, GY =     39, GZ =    189
AX = -12952, AY =   9258, AZ =  -7848		GX =  -1282, GY =     36, GZ =    191
AX = -12954, AY =   9280, AZ =  -7840		GX =  -1282, GY =     34, GZ =    192

总结

  • 内核将设备信息传递给 probe(),设备信息包括 i2c 设备的设备地址、i2c 设备所属的控制器。
    这样,驱动就可以操作控制器,向对应的 i2c 设备读写数据。
  • 每一个主设备号(major) 对应 /proc/devices 下的一个设备,使用 register_chrdev() 创建
  • 每一个次设备号(minor) 对应 /dev/ 下的一个设备文件,使用 mknod 或 device_create() 创建
  • 使用 class_create() 在 /sys/class/ 下面创建一个类,
    创建一个类不需要依赖特别的参数,可以理解为在内核任意处,使用任意名称,可以创建一个类
  • 使用 device_create() 可以在任意类下创建一个设备文件,会在 /sys/class/类/ 和 /dev/ 下均生成一个文件
    也就是说,一个设备可以随意放入任何一个类中。不过,设备是和驱动有关系的,因为要填入一个参数 MKDEV(major, 0)
  • 使用 device_create() 在 /dev/ 目录下创建一个设备的前提是先在 /sys/class/ 下先创建一个类(内核设计这样的架构是出于什么目的?面向对象?一个对象必须归属一个类?)
    使用 mknod 命令手动创建一个设备,不需要先创建一个类。

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

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

相关文章

web部署 三

案例: 1/在其中一个网站目录下创建\software文件夹&#xff0c;里面放txtppt/mp4/iso,文件类型。 2/web站点目录浏览启动应用。 3/用win10客户机浏览software目录下文件&#xff0c;并下载。txtppt/mp4/iso&#xff0c;发现问题并解决。 首先我们先建立一个software的文件夹并…

微服务初识

1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.1.单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打…

力扣串题:反转字符串中的元音字母

​​​​​​​ 双指针&#xff0c;注意判断是否为元音的操作 bool IsVowel(char s){if(sa||se||si||so||su||sA||sE||sI||sO||sU) return true;return false; }char * reverseVowels(char * s){int len strlen(s),i0;while(i<len-1){if(IsVowel(s[i])&&IsVowel(s…

【C语言】五种方法实现C语言中大小写字母的转化

文章目录 &#x1f4dd;tolower/toupper函数&#x1f309;tolower&#x1f320; toupper &#x1f320; ASCII码关系&#x1f309;位操作&#x1f309;宏定义 &#x1f320;小巧第五位&#x1f6a9;总结 &#x1f4dd;tolower/toupper函数 &#x1f309;tolower tolower函数是…

YOLOv7改进 | 更换主干网络之PP-LCNet

前言:Hello大家好,我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络,适用于多任务,并具有提高模型准确率的方法。与之前预测速度相近的模型相比,PP-LCNet具有更高的准确性。此外,对于计…

掘根宝典之C++普通迭代器和反向迭代器详解

简介 迭代器是一种用于遍历容器元素的对象。它提供了一种统一的访问方式&#xff0c;使程序员可以对容器中的元素进行逐个访问和操作&#xff0c;而不需要了解容器的内部实现细节。 C标准库里每个容器都定义了迭代器&#xff0c;这迭代器的名字就叫容器迭代器 迭代器的作用类…

数字电子技术笔记——组合逻辑功能

1.Adder&#xff08;加法器&#xff09; Half-Adder&#xff08;半加器&#xff09; Full-Adder&#xff08;全加器&#xff09; 74LS283(4-bit parallel adders) 74LS283 4-bit parallel adders 81 input 41 output carry look-ahead adder &#xff08;超前进位加法器&a…

牛客 NC266925 我不是大富翁(dp)

原题 首先记录这一道题的目的是提醒自己&#xff1a;动态规划的属性并不是只有 m a x max max&#xff0c; m i n min min 和 c o u n t count count&#xff0c;同时还有布尔类型的dp 这题不能考虑在距离的维度上思考&#xff0c;比如说看走几步走到哪里了&#xff0c;如果…

C++进阶之路---手把手带你学习AVL树

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#…

图像处理与视觉感知---期末复习重点(3)

文章目录 一、空间域和频率域二、傅里叶变换三、频率域图像增强 一、空间域和频率域 1. 空间域&#xff1a;即所说的像素域&#xff0c;在空间域的处理就是在像素级的处理&#xff0c;如在像素级的图像叠加。通过傅立叶变换后&#xff0c;得到的是图像的频谱&#xff0c;表示图…

【深度学习笔记】9_9 语义分割和数据集

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 9.9 语义分割和数据集 在前几节讨论的目标检测问题中&#xff0c;我们一直使用方形边界框来标注和预测图像中的目标。本节将探讨语义分…

深度学习基础知识之Atrous卷积(空洞卷积)

太久不看代码确实生疏了&#xff0c;盯着一堆不同的dilation&#xff0c;不知道有什么作用&#xff0c;论文中说是Atrous卷积&#xff0c;原来就是空洞卷积的意思。 Dilated/Atrous Convolution 空洞卷积&#xff08;膨胀卷积/扩张卷积&#xff09; 空洞卷积是一种不增加参数量…

Web 服务器-Tomcat

文章目录 Web服务器一、Tomcat简介二、基本使用三、在IDEA中创建Maven Web项目四、在IDEA中使用Tomcat Web服务器 一、Tomcat简介 二、基本使用 三、在IDEA中创建Maven Web项目 四、在IDEA中使用Tomcat

外包干了9天,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2018年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

【C#】.net core 6.0 使用第三方日志插件Log4net,日志输出到控制台或者文本文档

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

关于原型的一些总结

猛然发现太久没去复习了&#xff0c;于是复习了一些知识&#xff0c;顺便冒个泡。本次主要总结的知识点关于原型&#xff0c;再文章后半部分有原型相关的题&#xff0c;感兴趣的可直接观看。 一、原型 1.什么是原型 简单理解&#xff0c;原型就是一个对象&#xff0c;通过原…

Linux基础 想学好Linux请看这篇文章 Linux操作指令大全

当涉及学习 Linux 时&#xff0c;了解其基本原理和核心概念是至关重要的。Linux 是一种开源操作系统&#xff0c;广泛应用于服务器、嵌入式系统以及个人计算机中。它的灵活性、稳定性和安全性使得它成为许多 IT 专业人士和开发人员的首选。 第一步&#xff1a;了解基本概念和特…

CC连接过程

1、CC线连接过程 DFP和UFP会实时监控CC1和CC2引脚的电压&#xff0c;来评估DFP和UFP是否都已经在位。同时DFP可以根据电压确定自己所能提供的电流的大小 2、连接过程 Source端使用一个MOS管去控制Vbus&#xff0c;初始状态下&#xff0c;FET为关闭状态&#xff0c;Vbus不通。S…

苍穹外卖问题记录(持续更新)

Day01_3.2.4前后端联调 1. 前端无法登录 &#xff08;1&#xff09;确保nginx服务器已经启动 &#xff08;2&#xff09;查看自己数据库的用户名和密码是否和老师的一样&#xff0c;不一样的话需要在application-dev.yml文件中把老师的用户名密码修改成自己的 老师的用户名…

单⽬相机成像过程_看这一篇就够了

单⽬相机成像过程:看这一篇就够了 附赠宝贵的全套自动驾驶学习资料&#xff1a; 资料链接 附赠宝贵的全套自动驾驶学习资料&#xff1a; 资料链接