Linux字符设备驱动开发
本文介绍了树莓派连接MPU6050传感器的完整实现过程。首先通过硬件连接和I2C功能开启确保设备识别,然后修改设备树文件添加MPU6050节点配置。重点展示了字符设备驱动的开发过程,包括I2C通信实现、传感器初始化、数据读取等核心功能。驱动采用纯整数运算替代浮点运算,通过放大100倍的方式模拟两位小数显示,确保内核兼容性。最终用户可通过/dev/mpu6050设备文件读取加速度和陀螺仪数据,格式为
·
文章目录
1 硬件连接

2 开启树莓派I2C功能
# 1. 启用I2C
sudo raspi-config
→ 3 Interface Options → I2C → 选择Yes → 重启树莓派
# 2. 安装I2C工具(用于检测设备)
sudo apt install -y i2c-tools
# 3. 检测MPU6050是否被识别(地址默认0x68)
sudo i2cdetect -y 1
# 输出中出现“68” → 硬件连接成功
3 修改设备树
3.1 编辑树莓派设备树源文件
cd 你的内核源码目录/arch/arm64/boot/dts/broadcom
sudo vim bcm2711-rpi-4-b.dts
3.2 在I2C节点下添加MPU6050
&i2c1 {
status = "okay";
clock-frequency = <100000>; // I2C速率100kHz
// MPU6050节点(名称格式:设备名@地址)
mpu6050@68 {
compatible = "invensense,mpu6050"; // 驱动匹配的关键标识
reg = <0x68>; // MPU6050的I2C地址
status = "okay"; // 启用该节点
// 若使用中断,添加以下配置(INT接GPIO4):
// interrupt-parent = <&gpio>;
// interrupts = <4 IRQ_TYPE_EDGE_RISING>;
};
};
3.3 编译设备树并替换到SD卡
# 回到内核源码根目录,编译设备树
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
# 将编译后的DTB文件(bcm2711-rpi-4-b.dtb)拷贝到SD卡boot分区
sudo cp arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /mnt/rpi_boot/
4 编写MPU6050字符设备驱动(适配设备树)
驱动需通过设备树的compatible属性匹配MPU6050节点,实现I2C通信+字符设备接口,用户空间可通过/dev/mpu6050读取传感器数据。
// mpu6050_drv.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of.h>
// MPU6050寄存器定义
#define MPU6050_PWR_MGMT_1 0x6B // 电源管理寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B // 加速度X轴高8位
#define MPU6050_ACCEL_YOUT_H 0x3D // 加速度Y轴高8位
#define MPU6050_ACCEL_ZOUT_H 0x3F // 加速度Z轴高8位
#define MPU6050_GYRO_XOUT_H 0x43 // 陀螺仪X轴高8位
#define MPU6050_GYRO_YOUT_H 0x45 // 陀螺仪Y轴高8位
#define MPU6050_GYRO_ZOUT_H 0x47 // 陀螺仪Z轴高8位
// 字符设备相关定义
#define DEV_NAME "mpu6050"
#define DEV_CNT 1
static dev_t dev_num; // 设备号
static struct cdev mpu6050_cdev; // 字符设备结构体
static struct class *mpu6050_class; // 设备类
static struct i2c_client *g_client; // I2C客户端指针
// 读取MPU6050寄存器(16位有符号数)
static int mpu6050_read_reg(struct i2c_client *client, u8 reg, short *val)
{
u8 buf[2];
int ret;
ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf);
if (ret < 0) {
dev_err(&client->dev, "读取寄存器0x%02x失败\n", reg);
return ret;
}
*val = (buf[0] << 8) | buf[1];
return 0;
}
// 初始化MPU6050(唤醒传感器+配置量程)
static int mpu6050_init(struct i2c_client *client)
{
// 唤醒传感器(关闭睡眠)
if (i2c_smbus_write_byte_data(client, MPU6050_PWR_MGMT_1, 0x00) < 0) {
dev_err(&client->dev, "唤醒MPU6050失败\n");
return -EIO;
}
// 配置加速度计量程:±2g(0x00)
i2c_smbus_write_byte_data(client, 0x1C, 0x00);
// 配置陀螺仪量程:±2000°/s(0x18)
i2c_smbus_write_byte_data(client, 0x1B, 0x18);
dev_info(&client->dev, "MPU6050初始化成功\n");
return 0;
}
// ==================== 关键修改:去掉浮点运算,改用整数模拟两位小数 ====================
// 字符设备read接口:用户空间读取传感器数据(纯整数运算)
static ssize_t mpu6050_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
short accel_x, accel_y, accel_z;
short gyro_x, gyro_y, gyro_z;
char data[128];
int len;
// 整数版:放大100倍计算两位小数(避免浮点)
int accel_x_int, accel_y_int, accel_z_int;
int gyro_x_int, gyro_y_int, gyro_z_int;
// 读取传感器原始数据(16位有符号整数)
mpu6050_read_reg(g_client, MPU6050_ACCEL_XOUT_H, &accel_x);
mpu6050_read_reg(g_client, MPU6050_ACCEL_YOUT_H, &accel_y);
mpu6050_read_reg(g_client, MPU6050_ACCEL_ZOUT_H, &accel_z);
mpu6050_read_reg(g_client, MPU6050_GYRO_XOUT_H, &gyro_x);
mpu6050_read_reg(g_client, MPU6050_GYRO_YOUT_H, &gyro_y);
mpu6050_read_reg(g_client, MPU6050_GYRO_ZOUT_H, &gyro_z);
// 整数运算模拟两位小数(避免浮点):
// 加速度:±2g量程 → 16384 LSB/g → 1g = 16384 → 1/100g = 163.84 → 放大100倍:accel_x * 100 / 16384
accel_x_int = (accel_x * 100) / 16384;
accel_y_int = (accel_y * 100) / 16384;
accel_z_int = (accel_z * 100) / 16384;
// 陀螺仪:±2000°/s量程 → 131 LSB/(°/s) → 1°/s = 131 → 1/100°/s = 1.31 → 放大100倍:gyro_x * 100 / 131
gyro_x_int = (gyro_x * 100) / 131;
gyro_y_int = (gyro_y * 100) / 131;
gyro_z_int = (gyro_z * 100) / 131;
// 格式化数据:用整数拆分出“整数部分”和“小数部分”(替代%.2f)
len = snprintf(data, sizeof(data),
"加速度(X,Y,Z): %d.%02d, %d.%02d, %d.%02d g\n"
"陀螺仪(X,Y,Z): %d.%02d, %d.%02d, %d.%02d °/s\n",
// 加速度:整数部分=值/100,小数部分=值%100(补0到两位)
accel_x_int / 100, abs(accel_x_int) % 100,
accel_y_int / 100, abs(accel_y_int) % 100,
accel_z_int / 100, abs(accel_z_int) % 100,
// 陀螺仪:同上
gyro_x_int / 100, abs(gyro_x_int) % 100,
gyro_y_int / 100, abs(gyro_y_int) % 100,
gyro_z_int / 100, abs(gyro_z_int) % 100);
// 拷贝数据到用户空间
if (copy_to_user(buf, data, len)) {
return -EFAULT;
}
return len;
}
// 文件操作结构体
static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.read = mpu6050_read,
};
// 设备树匹配表(与设备树compatible一致)
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "invensense,mpu6050" },
{ /* 哨兵节点 */ }
};
MODULE_DEVICE_TABLE(of, mpu6050_of_match);
// 适配6.6内核的probe函数(仅i2c_client参数)
static int mpu6050_probe(struct i2c_client *client)
{
int ret;
g_client = client;
// 初始化MPU6050硬件
if (mpu6050_init(client) < 0) {
return -EIO;
}
// 动态分配设备号
ret = alloc_chrdev_region(&dev_num, 0, DEV_CNT, DEV_NAME);
if (ret < 0) {
dev_err(&client->dev, "分配设备号失败\n");
return ret;
}
// 初始化并添加字符设备
cdev_init(&mpu6050_cdev, &mpu6050_fops);
ret = cdev_add(&mpu6050_cdev, dev_num, DEV_CNT);
if (ret < 0) {
dev_err(&client->dev, "添加字符设备失败\n");
goto unregister_chrdev;
}
// 适配6.6内核的class_create(仅名称参数)
mpu6050_class = class_create(DEV_NAME);
if (IS_ERR(mpu6050_class)) {
ret = PTR_ERR(mpu6050_class);
dev_err(&client->dev, "创建设备类失败\n");
goto cdev_del;
}
// 创建设备文件(/dev/mpu6050)
device_create(mpu6050_class, NULL, dev_num, NULL, DEV_NAME);
dev_info(&client->dev, "MPU6050驱动加载成功,设备文件:/dev/mpu6050\n");
return 0;
// 错误处理:反向释放资源
cdev_del:
cdev_del(&mpu6050_cdev);
unregister_chrdev:
unregister_chrdev_region(dev_num, DEV_CNT);
return ret;
}
// I2C驱动remove函数
static void mpu6050_remove(struct i2c_client *client)
{
// 销毁设备文件和类
device_destroy(mpu6050_class, dev_num);
class_destroy(mpu6050_class);
// 删除字符设备并释放设备号
cdev_del(&mpu6050_cdev);
unregister_chrdev_region(dev_num, DEV_CNT);
dev_info(&client->dev, "MPU6050驱动卸载成功\n");
}
// 适配6.6内核的i2c_driver结构体
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
};
// 注册I2C驱动
module_i2c_driver(mpu6050_driver);
// 模块信息(必须声明GPL许可证)
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MPU6050字符设备驱动(适配Linux 6.6内核+树莓派4,纯整数运算)");
MODULE_AUTHOR("Custom");
5 编译驱动与设备树
5.1 编译驱动模块
创建Makefile(与mpu6050_drv.c同目录):
obj-m += mpu6050_drv.o
KDIR := 你的内核源码目录
PWD := $(shell pwd)
all:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KDIR) M=$(PWD) modules
clean:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C $(KDIR) M=$(PWD) clean
make
# 生成 mpu6050_drv.ko 模块文件
5.2 编译设备树
确保设备树已编译为DTB并替换到SD卡boot分区:
sudo cp 内核源码目录/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /mnt/rpi_boot/
6 测试驱动
- 将mpu6050_drv.ko拷贝到树莓派,加载驱动:
sudo insmod mpu6050_drv.ko
- 查看驱动是否匹配成功:
dmesg | grep mpu6050
# 输出“MPU6050设备匹配成功” → 设备树+驱动匹配成功
- 读取传感器数据
cat /dev/mpu6050
# 输出示例:
# Accel(X,Y,Z): 0.02, -0.01, 1.00 g
# Gyro(X,Y,Z): 0.12, -0.05, 0.03 °/s
关键说明:
设备树匹配:驱动通过of_device_id中的compatible与设备树节点的compatible一致,实现“自动匹配”;
I2C通信:驱动通过i2c_client与MPU6050通信,无需硬编码I2C地址(地址从设备树中读取);
字符设备接口:用户空间通过/dev/mpu6050直接读取传感器数据,符合Linux设备驱动规范。
若需卸载驱动,执行:
sudo rmmod mpu6050_drv
解决系统驱动冲突的代码:
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
// ==================== 寄存器/设备ID定义(核心兼容MPU6050/6500) ====================
#define MPU6050_WHOAMI_REG 0x75 // WhoAmI寄存器(读取设备ID)
#define MPU6050_PWR_MGMT_1_REG 0x6B // 电源管理寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B // 加速度X轴高8位
#define MPU6050_ACCEL_YOUT_H 0x3D // 加速度Y轴高8位
#define MPU6050_ACCEL_ZOUT_H 0x3F // 加速度Z轴高8位
#define MPU6050_GYRO_XOUT_H 0x43 // 陀螺仪X轴高8位
#define MPU6050_GYRO_YOUT_H 0x45 // 陀螺仪Y轴高8位
#define MPU6050_GYRO_ZOUT_H 0x47 // 陀螺仪Z轴高8位
#define MPU6050_DEVICE_ID 0x68 // MPU6050的WhoAmI值
#define MPU6500_DEVICE_ID 0x70 // MPU6500的WhoAmI值
// ==================== 字符设备基础定义 ====================
#define DEVICE_NAME "mpu6050"
#define DEVICE_COUNT 1
// ==================== 全局变量 ====================
static dev_t mpu6050_dev_num; // 设备号
static struct cdev mpu6050_cdev; // 字符设备结构体
static struct class *mpu6050_class; // 设备类
static struct i2c_client *mpu6050_client; // I2C客户端指针
// ==================== 寄存器读取函数(16位有符号数) ====================
static int mpu6050_read_reg_16(struct i2c_client *client, u8 reg, short *value)
{
u8 buf[2];
int ret;
ret = i2c_smbus_read_i2c_block_data(client, reg, 2, buf);
if (ret < 0) {
dev_err(&client->dev, "读取寄存器0x%02x失败, 错误码: %d\n", reg, ret);
return ret;
}
// 合并高低8位为16位有符号数
*value = (short)((buf[0] << 8) | buf[1]);
return 0;
}
// ==================== 设备ID检测函数(兼容6050/6500) ====================
static int mpu6050_check_device_id(struct i2c_client *client)
{
u8 whoami;
int ret;
// 读取WhoAmI寄存器(1字节)
ret = i2c_smbus_read_byte_data(client, MPU6050_WHOAMI_REG);
if (ret < 0) {
dev_err(&client->dev, "读取设备ID失败, 错误码: %d\n", ret);
return ret;
}
whoami = (u8)ret;
// 验证设备ID
if (whoami != MPU6050_DEVICE_ID && whoami != MPU6500_DEVICE_ID) {
dev_err(&client->dev, "设备ID不匹配! 读取到0x%02x, 期望0x%02x(MPU6050)或0x%02x(MPU6500)\n",
whoami, MPU6050_DEVICE_ID, MPU6500_DEVICE_ID);
return -ENODEV;
}
// 打印识别到的设备型号
dev_info(&client->dev, "检测到传感器: %s (ID=0x%02x)\n",
(whoami == MPU6050_DEVICE_ID) ? "MPU6050" : "MPU6500", whoami);
return 0;
}
// ==================== 传感器初始化函数 ====================
static int mpu6050_hw_init(struct i2c_client *client)
{
int ret;
// 第一步:检测设备ID
ret = mpu6050_check_device_id(client);
if (ret < 0) {
return ret;
}
// 第二步:唤醒传感器(关闭睡眠模式)
ret = i2c_smbus_write_byte_data(client, MPU6050_PWR_MGMT_1_REG, 0x00);
if (ret < 0) {
dev_err(&client->dev, "唤醒传感器失败, 错误码: %d\n", ret);
return ret;
}
// 第三步:配置加速度计量程(±2g,0x00)
ret = i2c_smbus_write_byte_data(client, 0x1C, 0x00);
if (ret < 0) {
dev_err(&client->dev, "配置加速度计失败, 错误码: %d\n", ret);
return ret;
}
// 第四步:配置陀螺仪量程(±2000°/s,0x18)
ret = i2c_smbus_write_byte_data(client, 0x1B, 0x18);
if (ret < 0) {
dev_err(&client->dev, "配置陀螺仪失败, 错误码: %d\n", ret);
return ret;
}
dev_info(&client->dev, "传感器初始化完成\n");
return 0;
}
// ==================== 字符设备READ接口(纯整数运算,无浮点) ====================
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
short accel_x, accel_y, accel_z;
short gyro_x, gyro_y, gyro_z;
char data_buf[128];
int len;
// 整数放大100倍模拟两位小数(规避浮点运算)
int acc_x, acc_y, acc_z;
int gyr_x, gyr_y, gyr_z;
// 读取加速度原始数据
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_ACCEL_XOUT_H, &accel_x) < 0) return -EIO;
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_ACCEL_YOUT_H, &accel_y) < 0) return -EIO;
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_ACCEL_ZOUT_H, &accel_z) < 0) return -EIO;
// 读取陀螺仪原始数据
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_GYRO_XOUT_H, &gyro_x) < 0) return -EIO;
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_GYRO_YOUT_H, &gyro_y) < 0) return -EIO;
if (mpu6050_read_reg_16(mpu6050_client, MPU6050_GYRO_ZOUT_H, &gyro_z) < 0) return -EIO;
// 整数运算转换为物理单位(放大100倍)
acc_x = (accel_x * 100) / 16384; // ±2g量程:16384 LSB/g
acc_y = (accel_y * 100) / 16384;
acc_z = (accel_z * 100) / 16384;
gyr_x = (gyro_x * 100) / 131; // ±2000°/s量程:131 LSB/(°/s)
gyr_y = (gyro_y * 100) / 131;
gyr_z = (gyro_z * 100) / 131;
// 格式化输出(拆分整数/小数部分,替代%.2f)
len = snprintf(data_buf, sizeof(data_buf),
"加速度(X,Y,Z): %d.%02d, %d.%02d, %d.%02d g\n"
"陀螺仪(X,Y,Z): %d.%02d, %d.%02d, %d.%02d °/s\n",
acc_x / 100, abs(acc_x) % 100,
acc_y / 100, abs(acc_y) % 100,
acc_z / 100, abs(acc_z) % 100,
gyr_x / 100, abs(gyr_x) % 100,
gyr_y / 100, abs(gyr_y) % 100,
gyr_z / 100, abs(gyr_z) % 100);
// 拷贝数据到用户空间
if (copy_to_user(buf, data_buf, len)) {
dev_err(&mpu6050_client->dev, "数据拷贝到用户空间失败\n");
return -EFAULT;
}
return len;
}
// ==================== 文件操作结构体 ====================
static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.read = mpu6050_read,
.llseek = no_llseek, // 禁止偏移,每次读取最新数据
};
// ==================== 设备树匹配表(与设备树compatible对应) ====================
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "invensense,mpu6050" },
{ /* 哨兵节点 */ }
};
MODULE_DEVICE_TABLE(of, mpu6050_of_match);
// ==================== I2C驱动PROBE函数(适配6.6内核,仅i2c_client参数) ====================
static int mpu6050_probe(struct i2c_client *client)
{
int ret;
// 保存I2C客户端指针
mpu6050_client = client;
// 第一步:初始化传感器硬件
ret = mpu6050_hw_init(client);
if (ret < 0) {
return ret;
}
// 第二步:动态分配设备号
ret = alloc_chrdev_region(&mpu6050_dev_num, 0, DEVICE_COUNT, DEVICE_NAME);
if (ret < 0) {
dev_err(&client->dev, "分配设备号失败, 错误码: %d\n", ret);
return ret;
}
// 第三步:初始化字符设备
cdev_init(&mpu6050_cdev, &mpu6050_fops);
ret = cdev_add(&mpu6050_cdev, mpu6050_dev_num, DEVICE_COUNT);
if (ret < 0) {
dev_err(&client->dev, "添加字符设备失败, 错误码: %d\n", ret);
goto err_unregister;
}
// 第四步:创建设备类(适配6.6内核,仅名称参数)
mpu6050_class = class_create(DEVICE_NAME);
if (IS_ERR(mpu6050_class)) {
ret = PTR_ERR(mpu6050_class);
dev_err(&client->dev, "创建设备类失败, 错误码: %d\n", ret);
goto err_cdev_del;
}
// 第五步:创建设备文件(/dev/mpu6050)
if (IS_ERR(device_create(mpu6050_class, NULL, mpu6050_dev_num, NULL, DEVICE_NAME))) {
dev_err(&client->dev, "创建设备文件失败\n");
ret = -EIO;
goto err_class_destroy;
}
dev_info(&client->dev, "%s驱动加载成功! 设备文件: /dev/%s\n", DEVICE_NAME, DEVICE_NAME);
return 0;
// 错误处理(反向释放资源)
err_class_destroy:
class_destroy(mpu6050_class);
err_cdev_del:
cdev_del(&mpu6050_cdev);
err_unregister:
unregister_chrdev_region(mpu6050_dev_num, DEVICE_COUNT);
return ret;
}
// ==================== I2C驱动REMOVE函数 ====================
static void mpu6050_remove(struct i2c_client *client)
{
// 销毁设备文件
device_destroy(mpu6050_class, mpu6050_dev_num);
// 销毁设备类
class_destroy(mpu6050_class);
// 删除字符设备
cdev_del(&mpu6050_cdev);
// 释放设备号
unregister_chrdev_region(mpu6050_dev_num, DEVICE_COUNT);
dev_info(&client->dev, "%s驱动卸载成功\n", DEVICE_NAME);
}
// ==================== I2C驱动结构体(适配6.6内核) ====================
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = DEVICE_NAME,
.of_match_table = mpu6050_of_match,
.owner = THIS_MODULE,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
};
// ==================== 模块注册/卸载 ====================
module_i2c_driver(mpu6050_driver);
// ==================== 模块信息 ====================
MODULE_LICENSE("GPL"); // 必须声明GPL,否则内核拒绝加载
MODULE_AUTHOR("Custom");
MODULE_DESCRIPTION("MPU6050/6500驱动(适配Linux 6.6+树莓派4+Debian 13 Trixie)");
MODULE_VERSION("1.0");
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)