当前位置: 首页 > 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文库

相关文章:

  • 服务器硬盘分类
  • 服务器磁盘按阵列划分为哪几类
  • 【Vue】将响应式对象转为非响应式对象
  • (37)服务器增加ipv6配置方法
  • 浪潮Inspur服务器产品线概述
  • van-picker实现日期时间选择器
  • fatal error: uuid/uuid.h: No such file or directory 编译问题修复。
  • linux杀死进程自身
  • 准备好,开始构建:由 Elasticsearch 向量数据库驱动的 Red Hat OpenShift AI 应用程序
  • linux二进制安装mysql:
  • STM32之模数转换器(ADC)
  • 第三次中医知识问答模型微调
  • FFmpeg解码器配置指南:为什么--enable-decoders不能单独使用?
  • 【CSS】九宫格布局
  • Windos11家庭版安装本地安全策略
  • 线程池线程数配置
  • LeRobot的机器人控制系统(上)
  • Python 响应报文提取方式
  • 完整改进RIME算法,基于修正多项式微分学习算子Rime-ice增长优化器,完整MATLAB代码获取
  • [Linux]Linux多线程编程技术探讨(代码示例)
  • 进行网站开发前 需要干什么/企业seo优化服务
  • 想给学校社团做网站/百度一下首页登录入口
  • 网站开发者的常用工具/建站优化公司
  • 石家庄物流网站建设/快速排名教程
  • 网站图标素材/杭州网络整合营销公司
  • 菏泽城乡住房建设局网站/模板网站免费