当前位置: 首页 > news >正文

结课作业自选01. 内核空间 MPU6050 体感鼠标驱动程序(二)(完整实现流程)

目录

一. 题目要求-内核空间 MPU6050 体感鼠标驱动程序

二. 伪代码及程序运行流程

三. 主要函数详解(根据代码流程进行详解)

3.1 module_i2c_driver宏(对应“1”)

3.2 mpu_of_match设备树匹配表(对应“2”)

3.3 MODULE_DEVICE_TABLE宏声明驱动支持的设备列表

3.4 mpu_mouse_probe驱动检测函数(初始化设备)(对应“3”)

3.4.1 init_gpio_reg初始化GPIO时钟和寄存器映射

3.5 timer_callback回调函数(对应“4、5”)

3.6 accel_work_handler 工作队列处理函数(定时读取数据)(对应“6、7”)

3.6.1 read_accel加速度读取函数

3.6.1.1 i2c_smbus_read_i2c_block_data函数功能

3.6.1.2 convert_accel函数 转换加速度为位移

3.6.2 lowpass_filter低通滤波函数

四. 完整版代码


一. 题目要求-内核空间 MPU6050 体感鼠标驱动程序

        (1)采用课上练习“设备驱动练习”给出的 i2c 框架程序

        (2)修改 dts 文件时,按照课件中的描述修改。

        (3)实现驱动模型要求的 probe()函数。

注意:

        鼠标功能除了 MPU6050 运动传感器外还需要一个或两个按键(左右键),

        按键可以使用按键中断或者使用 MPU6050 定时器同步读取电平。        

        自己设计滤波程序使鼠标指针稳定。

        (4)实现 mpu6050 的定时服务函数,并向 input 核心层报告事件。可以自己选择实

现鼠标类设备还是触摸屏类设备。

        (5)编写一个应用程序来测试驱动,读出鼠标坐标值和按键事件。

二. 伪代码及程序运行流程

        代码执行流程和前一个博客中,内核使用mpu6050的流程一致,此代码就是在前代码的基础上修改完成的。

        1. 模块加载insmod mpu6050_kernel.ko时module_i2c_driver宏会自动注册

        2. mpu_mouse_driver结构体里的.probe对应的mpu_mouse_probe在I2C总线检测到匹配的设备时执行(根据mpu_mouse_driver结构体中的.of_match_table设备树匹配表进行匹配检测)

        3. mpu_mouse_probe在进行一系列设备初始化之后执行mod_timer函数定时器计时

        4. 定时器到期之后执行定时器回调函数timer_callback

        5. timer_callback里面执行data->work,也就是accel_work_handler工作队列处理函数

        6. accel_work_handler函数读取加速度并传给虚拟鼠标控制鼠标实现体感鼠标功能

        7. accel_work_handler函数最后执行mod_timer函数,重新给定时器计时,然后回到4.一直循环执行,直到rmmod mpu6050_kernel.ko为止

// mpu_mouse_kernel.cstruct mpu_mouse_data {// 代码中用到的主要变量
};/* 加速度转换函数(纯整数运算) */
static void convert_accel(int16_t raw_x, int16_t raw_y, int *dx, int *dy) {// 将加速度转换成鼠标的位移
}/*  低通滤波函数:filtered_val = (new_val + 3*last)/4 */
static void lowpass_filter(int *filtered_val, int new_val) {// 低通滤波
}/* 读取加速度计数据 */
static void read_accel(struct i2c_client *client, int *dx, int *dy) {// 从MPU6050寄存器读取原始数据(加速度XYZ)i2c_smbus_read_i2c_block_data(client, 0x3B, 6, buf);// 转换加速度为位移(注意,这里是将raw_y传给了x, raw_x传给了y, 因为mpu和屏幕的xy轴是相反的)convert_accel(raw_y, raw_x, dx, dy);
}/* 新增:初始化GPIO时钟和寄存器映射(基于gpios.c中的myopen函数逻辑) */
static void init_gpio_reg(struct mpu_mouse_data *data) {// 映射APER_CLK并启用GPIO时钟(复用gpios.c中的逻辑)// 映射GPIO_DATA2寄存器(复用gpios.c中的逻辑)
}// 6. accel_work_handler函数读取加速度并传给虚拟鼠标控制鼠标实现体感鼠标功能
// 7. accel_work_handler函数最后执行mod_timer函数,重新给定时器计时,然后回到4.一直循环执行,直到rmmod mpu6050_kernel.ko为止
/* 修改:在工作队列处理函数中添加按键检测(新增代码) */
/* 工作队列处理函数(定时读取数据) */
static void accel_work_handler(struct work_struct *work) {// 读取加速度及按键事件并上报// 重新调度定时器mod_timer(&data->timer, jiffies + msecs_to_jiffies(SAMPLE_INTERVAL));
}// 5. timer_callback里面执行data->work,也就是accel_work_handler工作队列处理函数
/* 定时器回调函数(触发工作队列) */
static void timer_callback(struct timer_list *t) {struct mpu_mouse_data *data = from_timer(data, t, timer);schedule_work(&data->work);
}// 3. mpu_mouse_probe在进行一系列设备初始化之后执行mod_timer函数定时器倒计时
// 4. 定时器到期之后执行定时器回调函数timer_callback
/* 修改:在probe函数中初始化GPIO(新增代码) */
/* 驱动探测函数(初始化设备) */
static int mpu_mouse_probe(struct i2c_client *client, const struct i2c_device_id *id) {// 设备初始化// 初始化定时器和工作队列timer_setup(&data->timer, timer_callback, 0);INIT_WORK(&data->work, accel_work_handler);mod_timer(&data->timer, jiffies + msecs_to_jiffies(SAMPLE_INTERVAL));return 0;
}/* 修改:在remove函数中释放GPIO映射(新增代码) */
/* 驱动移除函数(释放资源) */
static int mpu_mouse_remove(struct i2c_client *client) {// +++ 新增:取消GPIO寄存器映射// 清理定时器和工作队列
}/* 设备树匹配表 */
static const struct of_device_id mpu_of_match[] = {{ .compatible = "inv,mpu6050" },    // 必须与设备树中的compatible字段一致{ }
};
MODULE_DEVICE_TABLE(of, mpu_of_match);// 2. mpu_mouse_driver结构体里的.probe对应的mpu_mouse_probe在I2C总线检测到匹配的设备时执行
/* I2C驱动结构体 */
static struct i2c_driver mpu_mouse_driver = {.probe = mpu_mouse_probe,.remove = mpu_mouse_remove,.driver = {.name = "mpu6050-mouse",.of_match_table = mpu_of_match,     // 启用设备树匹配},
};// 1. 模块加载insmod mpu6050_kernel.ko时module_i2c_driver宏会自动注册
module_i2c_driver(mpu_mouse_driver);
MODULE_DESCRIPTION("MPU6050 I2C Mouse Driver with GPIO Buttons");
MODULE_LICENSE("GPL");

三. 主要函数详解(根据代码流程进行详解)

3.1 module_i2c_driver宏(对应“1”)

        1. 模块加载insmod mpu6050_kernel.ko时module_i2c_driver宏会自动注册

        1. 自动生成模块的加载/卸载函数

        开发者只需定义一个 i2c_driver 结构体,并通过 module_i2c_driver 宏将其绑定,即可自动生成以下代码:

module_init(i2c_driver_probe);  // 模块加载时调用 i2c_add_driver
module_exit(i2c_driver_remove); // 模块卸载时调用 i2c_del_driver

        无需手动编写 module_init 和 module_exit

        2. 封装驱动注册与注销

        宏内部通过调用 i2c_add_driver 和 i2c_del_driver 完成以下操作:

        注册驱动:将 i2c_driver 注册到内核的 I2C 子系统。

        注销驱动:在模块卸载时,安全地移除驱动并释放资源。

        3. 本代码解释

static struct i2c_driver mpu_mouse_driver = {.probe = mpu_mouse_probe,.remove = mpu_mouse_remove,.driver = {.name = "mpu6050-mouse",.of_match_table = mpu_of_match,},
};
module_i2c_driver(mpu_mouse_driver);

        注册流程
        当通过 insmod 加载驱动模块时,module_i2c_driver 会自动调用 i2c_add_driver(&mpu_mouse_driver),触发设备探测(.probe 函数)。

        注销流程
        当通过 rmmod 卸载模块时,自动调用 i2c_del_driver(&mpu_mouse_driver),执行 .remove 函数清理资源。

        driver.name:驱动名称(需唯一)。

        of_match_table(可选):设备树匹配表。

3.2 mpu_of_match设备树匹配表(对应“2”)

        2. mpu_mouse_driver结构体里的.probe对应的mpu_mouse_probe在I2C总线检测到匹配的设备时执行(根据mpu_mouse_driver结构体中的.of_match_table设备树匹配表进行匹配检测)

        设备树文件中i2c连接mpu6050的compatible 字段如下,mpu_of_match中的compatible 字段与设备树中的compatible 字段相同,即为匹配成功,然后执行mpu_mouse_probe函数。

3.3 MODULE_DEVICE_TABLE宏声明驱动支持的设备列表

   MODULE_DEVICE_TABLE 是 Linux 内核中一个关键的宏,用于 声明驱动支持的设备列表,并帮助内核实现模块与设备的动态匹配。以下是其具体作用和实现细节: 

static const struct of_device_id mpu_of_match[] = {{ .compatible = "inv,mpu6050" }, // 与设备树节点中的 compatible 字段匹配{ }
};
MODULE_DEVICE_TABLE(of, mpu_of_match); // 关键宏声明

具体流程

        模块加载时

   1. 内核解析模块中的 MODULE_DEVICE_TABLE(of, ...),将兼容性字符串(如 "inv,mpu6050")注册到全局设备树匹配表。

        设备树解析时

        2. 内核启动时,设备树中的节点若包含 compatible = "inv,mpu6050",则会触发匹配逻辑。

        驱动绑定

        3. 内核调用匹配驱动的 .probe 函数(即 mpu_mouse_probe),完成硬件初始化。

3.4 mpu_mouse_probe驱动检测函数(初始化设备)(对应“3”)

        3. mpu_mouse_probe在进行一系列设备初始化之后执行mod_timer函数定时器计时

/* 修改:在probe函数中初始化GPIO(新增代码) */
/* 驱动探测函数(初始化设备) */
static int mpu_mouse_probe(struct i2c_client *client, const struct i2c_device_id *id) {struct device *dev = &client->dev;  // 获取与当前 I2C 设备关联的通用设备结构体指针struct mpu_mouse_data *data;int ret;// 分配设备数据结构data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);if (!data) return -ENOMEM;// 初始化I2C客户端data->client = client;i2c_set_clientdata(client, data);// 初始化输入设备data->input = devm_input_allocate_device(dev);if (!data->input) return -ENOMEM;data->input->name = "MPU6050 Mouse";data->input->id.bustype = BUS_I2C;// !!! 修改:注册按键事件类型(新增EV_KEY支持)__set_bit(EV_REL, data->input->evbit);__set_bit(REL_X, data->input->relbit);__set_bit(REL_Y, data->input->relbit);__set_bit(EV_KEY, data->input->evbit);      // +++ 新增按键事件__set_bit(BTN_LEFT, data->input->keybit);   // +++ 左键__set_bit(BTN_RIGHT, data->input->keybit);  // +++ 右键// 注册输入设备ret = input_register_device(data->input);if (ret) {dev_err(dev, "Failed to register input device\n");return ret;}// +++ 新增:初始化GPIO寄存器init_gpio_reg(data);// 初始化MPU6050i2c_smbus_write_byte_data(client, 0x1C, 0x00);i2c_smbus_write_byte_data(client, 0x6B, 0x00);msleep(100);data->filtered_dx = 0;data->filtered_dy = 0;// +++ 新增:初始化按键状态和去抖动计数器data->prev_left_state = 1; // 默认未按下data->prev_right_state = 1;// 初始化定时器和工作队列timer_setup(&data->timer, timer_callback, 0);INIT_WORK(&data->work, accel_work_handler);mod_timer(&data->timer, jiffies + msecs_to_jiffies(SAMPLE_INTERVAL));dev_info(dev, "MPU6050 Mouse Driver Initialized\n");return 0;
}

3.4.1 init_gpio_reg初始化GPIO时钟和寄存器映射

        仿照gpio内核驱动代码改写

/* 新增:初始化GPIO时钟和寄存器映射(基于gpios.c中的myopen函数逻辑) */
static void init_gpio_reg(struct mpu_mouse_data *data) {unsigned int *clk_reg;// 映射APER_CLK并启用GPIO时钟(复用gpios.c中的逻辑)clk_reg = ioremap(APER_CLK, 4);if (clk_reg) {iowrite32(ioread32(clk_reg) | 1 << 22, clk_reg); // 设置第22位iounmap(clk_reg);} else {pr_err("Failed to map APER_CLK register\n");}// 映射GPIO_DATA2寄存器(复用gpios.c中的逻辑)data->gpio_reg = ioremap(GPIO_DATA2, 4);if (!data->gpio_reg) {pr_err("Failed to map GPIO_DATA2 register\n");}
}

3.5 timer_callback回调函数(对应“4、5”)

        4. 定时器到期之后执行定时器回调函数timer_callback

        5. timer_callback里面执行data->work,也就是accel_work_handler工作队列处理函数

/* 定时器回调函数(触发工作队列) */
static void timer_callback(struct timer_list *t) {struct mpu_mouse_data *data = from_timer(data, t, timer);schedule_work(&data->work);
}

        schedule_work(&data->work) 的作用是 将工作项 data->work 提交到内核的全局工作队列中,以便在进程上下文中异步执行 accel_work_handler 函数,确保内核的实时性和稳定性,避免中断处理被阻塞。

3.6 accel_work_handler 工作队列处理函数(定时读取数据)(对应“6、7”)

        6. accel_work_handler函数读取加速度并传给虚拟鼠标控制鼠标实现体感鼠标功能

        7. accel_work_handler函数最后执行mod_timer函数,重新给定时器计时,然后回到4.一直循环执行,直到rmmod mpu6050_kernel.ko为止

        按键状态的获取和判断也在此函数中

/* 修改:在工作队列处理函数中添加按键检测(新增代码) */
/* 工作队列处理函数(定时读取数据) */
static void accel_work_handler(struct work_struct *work) {struct mpu_mouse_data *data = container_of(work, struct mpu_mouse_data, work);int dx, dy;uint32_t gpio_state;int current_left, current_right;// 读取加速度并滤波read_accel(data->client, &dx, &dy);lowpass_filter(&data->filtered_dx, dx);lowpass_filter(&data->filtered_dy, dy);// 上报相对位移事件input_report_rel(data->input, REL_X, data->filtered_dx);input_report_rel(data->input, REL_Y, data->filtered_dy);// +++ 新增:读取GPIO状态并上报按键事件if (data->gpio_reg) {gpio_state = ioread32(data->gpio_reg);current_left = !(gpio_state & 0x04); // bit2=0表示左键按下    current_left=1是按下,=0是未按下current_right = !(gpio_state & 0x02); // bit1=0表示右键按下/* 无按键按下:37f:0011 0111 0111左按键按下:37b:0011 0111 1011右按键按下:37d:0011 0111 1101 */// +++ 新增:去抖动逻辑if (current_left != data->prev_left_state || current_right != data->prev_right_state) {// 状态稳定后上报按键事件input_report_key(data->input, BTN_LEFT, current_left);input_report_key(data->input, BTN_RIGHT, current_right);// 更新上一次状态data->prev_left_state = current_left;data->prev_right_state = current_right;}}input_sync(data->input);// 重新调度定时器mod_timer(&data->timer, jiffies + msecs_to_jiffies(SAMPLE_INTERVAL));
}

3.6.1 read_accel加速度读取函数

/* 读取加速度计数据 */
static void read_accel(struct i2c_client *client, int *dx, int *dy) {uint8_t buf[6];int16_t raw_x, raw_y;// 从MPU6050寄存器读取原始数据(加速度XYZ)i2c_smbus_read_i2c_block_data(client, 0x3B, 6, buf);// 合并高8位和低8位数据raw_x = (buf[0] << 8) | buf[1];raw_y = (buf[2] << 8) | buf[3];// 转换加速度为位移(注意,这里是将raw_y传给了x, raw_x传给了y, 因为mpu和屏幕的xy轴是相反的)convert_accel(raw_y, raw_x, dx, dy);
}
3.6.1.1 i2c_smbus_read_i2c_block_data函数功能

        指定寄存器地址:通过 reg 参数指定要读取的寄存器起始地址。

        封装位置Linux 内核的 i2c-core-smbus.c 文件。

3.6.1.2 convert_accel函数 转换加速度为位移

        因为强制使用浮点运算会导致代码无法运行,纯整数运算确保驱动在不同硬件平台上的通用性。所以这里使用整数运算,通过设置合理的灵敏度、死区等宏定义参数,确保代码稳定运行。

/* 加速度转换函数(纯整数运算) */
static void convert_accel(int16_t raw_x, int16_t raw_y, int *dx, int *dy) {// 1. 原始数据转实际加速度(cm/s²)int ax = (raw_x * GRAVITY_CM_S2) / ACCEL_SCALE_2G;int ay = (raw_y * GRAVITY_CM_S2) / ACCEL_SCALE_2G;// 2. 应用死区滤波ax = (abs(ax) < DEADZONE) ? 0 : ax;ay = (abs(ay) < DEADZONE) ? 0 : ay;// 3. 加速度转位移(灵敏度调整)*dx = (ax * SENSITIVITY) / 100;  // 整数运算避免浮点*dy = -(ay * SENSITIVITY) / 100; // Y轴方向取反
}

3.6.2 lowpass_filter低通滤波函数

/*  低通滤波函数:filtered_val = (new_val + 3*last)/4 */
static void lowpass_filter(int *filtered_val, int new_val) {*filtered_val = (new_val + 3 * (*filtered_val)) / 4;
}

1. 低通滤波函数的作用

        (1)抑制高频噪声
        通过衰减信号中的快速变化部分(如传感器噪声、瞬时干扰),保留低频成分(如真实运动趋势),使数据更平滑稳定。

        (2)平滑信号输出
        减少测量值的突变,提升数据的可读性和后续处理的可靠性(如鼠标移动控制)。

2. 为什么使用这个公式(此公式的优势)

        (1)计算高效

        仅需 一次乘法、一次加法、一次除法(或位运算),适合实时处理。示例代码中,除法为整数运算(/4),可优化为右移操作(>> 2),进一步提升速度。

        (2)内存占用极低

        只需保存 上一次滤波值,无需存储多组历史数据(如移动平均滤波需保存N个样本)。

        (3)参数可调性强

        通过调整权重比例(如 (new_val + 7 * filtered_val) / 8),可灵活控制平滑效果与响应速度的平衡。

        (4)避免浮点运算

        纯整数运算兼容无FPU的嵌入式平台,减少内核上下文切换开销。

        (5)平滑效果显著

        在MPU6050驱动中,能有效抑制加速度计的抖动噪声,使鼠标移动更平滑。

四. 完整版代码

        内核空间MPU6050体感鼠标驱动程序资源-CSDN文库

http://www.xdnf.cn/news/586063.html

相关文章:

  • 网络编程 之 从BIO到 NIO加多线程高性能网络编程实战
  • 嵌入式学习笔记 - Void类型的指针
  • FFmpeg解码器配置指南:为什么--enable-decoders不能单独使用?
  • YOLOv11 性能评估与横向对比
  • Vault应用广吗?我是否有学习使用的必要,难不难?
  • 解码工业转型密码,R‘AIN SUITE赋能制造业价值跃迁
  • labview设计一个虚拟信号发生器
  • 齿轮,链轮,同步轮,丝杆传动sw画法
  • 训练一个线性模型
  • Linux 线程(中)
  • 基于FPGA控制电容阵列与最小反射算法的差分探头优化设计
  • 使用pm2 部署react+nextjs项目到服务器
  • (Java基础笔记vlog)Java中常见的几种设计模式详解
  • java接口自动化(四) - 企业级代码管理工具Git的应用
  • 理解全景图像拼接
  • 动态网页爬取:Python如何获取JS加载的数据?
  • Jenkins与Maven的集成配置
  • C++中的string(1)简单介绍string中的接口用法以及注意事项
  • Web前端开发 - 制作简单的焦点图效果
  • 单例模式的运用
  • UniApp+Vue3微信小程序二维码生成、转图片、截图保存整页
  • uniapp实现的简约美观的票据、车票、飞机票模板
  • ffmpeg 转换视频格式
  • 【Windows】FFmpeg安装教程
  • 「Python教案」运算符的使用
  • 中国计算机学会——2024年9月等级考试5级——第四题、森森快递(贪心+线段树)
  • JavaScriptAPIs学习day3--事件高级
  • 破局制造业转型: R²AIN SUITE 提效实战教学
  • Unity3D 异步加载材质显示问题排查
  • Python安全密码生成器:告别弱密码的最佳实践