如果你希望在使用Systemd服务运行的两个I2C读写脚本中,其中一个脚本延迟10秒运行,可以通过多种方式实现。这里我们将讨论如何在同一个Systemd服务单元文件中实现这一目标,以及如何通过创建两个独立的服务并设置依赖关系和延迟来实现这一目标。
### 方法一:在同一个Service单元文件中延迟执行第二个脚本
你可以在同一个Service单元文件中使用bash命令来延迟执行第二个脚本。下面是一个示例:
#### 步骤:
1. **创建服务单元文件**
创建一个新的Systemd服务单元文件,例如`i2c-read-write.service`:
```sh
sudo nano /etc/systemd/system/i2c-read-write.service
```
2. **添加服务配置**
在这个文件中添加以下内容:
```ini
[Unit]
Description=I2C Read Write Script with Delay
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c '/path/to/i2c_read_write_1.sh; sleep 10; /path/to/i2c_read_write_2.sh'
[Install]
WantedBy=multi-user.target
```
解释:
- **[Unit]**部分定义了服务的描述和启动顺序。
- **[Service]**部分定义了具体的服务行为。`ExecStart`使用Bash命令依次执行第一个脚本、延迟10秒后再执行第二个脚本。
- **[Install]**部分定义了该服务的目标,即在多用户模式下启动。
3. **保存并退出编辑器**
按 `Ctrl+X`,然后按 `Y` 保存并退出 `nano` 编辑器。
4. **启用和启动服务**
运行以下命令以启用并启动服务:
```sh
sudo systemctl enable i2c-read-write.service
sudo systemctl start i2c-read-write.service
```
5. **检查服务状态**
你可以使用以下命令来检查服务状态,以确保服务成功启动并运行:
```sh
sudo systemctl status i2c-read-write.service
```
### 方法二:创建两个独立的Service单元文件并设置依赖关系和延迟
你也可以创建两个独立的Systemd服务单元文件,并在其中一个服务中设置延迟,然后确保第一个服务在第二个服务之前执行。
#### 步骤:
1. **创建第一个服务单元文件**
创建一个服务单元文件,比如`i2c-read-write-1.service`:
```sh
sudo nano /etc/systemd/system/i2c-read-write-1.service
```
添加以下内容:
```ini
[Unit]
Description=I2C Read Write Script 1
After=network.target
[Service]
Type=oneshot
ExecStart=/path/to/i2c_read_write_1.sh
[Install]
WantedBy=multi-user.target
```
2. **创建第二个服务单元文件**
创建另一个服务单元文件,比如`i2c-read-write-2.service`:
```sh
sudo nano /etc/systemd/system/i2c-read-write-2.service
```
添加以下内容:
```ini
[Unit]
Description=I2C Read Write Script 2
After=i2c-read-write-1.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'sleep 10; /path/to/i2c_read_write_2.sh'
[Install]
WantedBy=multi-user.target
```
解释:
- **[Unit]**部分中,`After=i2c-read-write-1.service`确保第二个脚本在第一个脚本之后执行。
- **[Service]**部分中,`ExecStart`使用Bash命令延迟10秒后再执行第二个脚本。
3. **保存并退出编辑器**
按 `Ctrl+X`,然后按 `Y` 保存并退出每个 `nano` 编辑器。
4. **启用和启动两个服务**
```sh
sudo systemctl enable i2c-read-write-1.service
sudo systemctl enable i2c-read-write-2.service
sudo systemctl start i2c-read-write-1.service
sudo systemctl start i2c-read-write-2.service
```
5. **检查服务状态**
你可以分别检查两个服务的状态:
```sh
sudo systemctl status i2c-read-write-1.service
sudo systemctl status i2c-read-write-2.service
```
### 总结
通过以上两种方法,你可以使用Systemd服务在运行两个I2C读写脚本时,使其中一个脚本延迟10秒执行。第一种方法更简单,适合脚本逻辑简单的情况。第二种方法更灵活,适合需要更精细的控制和依赖管理的情况。选择哪种方法取决于你的具体需求和系统配置。
在Ubuntu系统中,有多种方法可以查看当前系统的版本信息。以下是几个常用的方法:
### 方法一:使用 `lsb_release` 命令
`lsb_release` 命令是获取Linux Standard Base (LSB) 和特定发行版信息的一种通用工具。
运行以下命令来查看Ubuntu版本:
```sh
lsb_release -a
```
这会输出类似以下的信息:
```plaintext
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.1 LTS
Release: 20.04
Codename: focal
```
### 方法二:查看 `/etc/os-release` 文件
你也可以查看 `/etc/os-release` 文件,该文件包含了关于系统版本的信息。
运行以下命令:
```sh
cat /etc/os-release
```
这会输出类似以下的信息:
```plaintext
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```
### 方法三:查看 `/etc/issue` 文件
`/etc/issue` 文件通常包含系统的简单描述信息。
运行以下命令:
```sh
cat /etc/issue
```
这会输出类似以下的信息:
```plaintext
Ubuntu 20.04.1 LTS \n \l
```
### 方法四:使用 `hostnamectl` 命令
`hostnamectl` 命令既可以查看主机名,也可以查看操作系统的版本信息。
运行以下命令:
```sh
hostnamectl
```
这会输出类似以下的信息:
```plaintext
Static hostname: yourhostname
Icon name: computer-vm
Chassis: vm
Machine ID: **************
Boot ID: **************
Virtualization: kvm
Operating System: Ubuntu 20.04.1 LTS
Kernel: Linux 5.4.0-42-generic
Architecture: x86-64
```
### 方法五:通过图形用户界面
如果你使用的是带GUI的Ubuntu桌面版,可以通过以下步骤查看版本信息:
1. 点击屏幕右上角的系统菜单。
2. 选择 "Settings" 或 "System Settings"。
3. 在左侧菜单中选择 "About" 或 "Details"。
4. 在右侧面板中可以看到操作系统的版本信息。
### 总结
上述方法都可以用来查看Ubuntu系统的版本信息。你可以根据自己的需求和环境选择最方便的一种方法。
当在 Linux 系统中使用 `devm_request_threaded_irq` 成功注册 IRQ 后,硬件中断成功产生但没有调用 `irq_handler` 函数,同时使用 `cat /proc/interrupts` 确认中断计数也没有增加,这种情况下可能涉及多个方面的问题。以下是常见的原因和排查步骤:
### 1. 硬件问题
#### 确认硬件是否确实在触发中断
- 使用示波器或其他硬件调试工具验证中断信号是否实际产生。
- 检查硬件配置和连接,确保中断发生条件满足。
### 2. 中断控制器配置问题
#### 确认中断控制器是否正确配置
- 检查中断控制器(如 GPIO 中断控制器、APIC 等)的寄存器配置,确保中断信号能够被正确识别。
- 确认设备树或平台数据中的中断配置正确。
### 3. 内核配置问题
#### 确认内核配置是否正确支持中断
- 检查内核配置文件(.config),确认是否启用了中断相关的配置选项。
- 确保未禁用中断相关的内核功能。
### 4. 中断标志配置问题
#### 检查传递给 `devm_request_threaded_irq` 的标志
- 确认使用了正确的中断标志,如 `IRQF_SHARED`, `IRQF_TRIGGER_RISING`, `IRQF_TRIGGER_FALLING`, `IRQF_TRIGGER_HIGH`, 或 `IRQF_TRIGGER_LOW` 等,根据硬件要求配置正确的标志。
### 5. 中断屏蔽问题
#### 确认中断未被屏蔽
- 检查中断屏蔽寄存器,确保中断未被屏蔽。
- 在驱动程序中使用 `enable_irq` 和 `disable_irq` 确认中断是否被有效启用。
### 6. 中断号映射问题
#### 确认注册的中断号正确
- 使用适当的方法获取中断号,并确认其有效性。
- 确认设备树或平台数据中的中断号正确。
### 示例代码
以下是一个典型的驱动程序示例,确保正确配置了中断:
```c
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
static irqreturn_t my_irq_handler(int irq, void *dev_id) {
printk(KERN_INFO "IRQ Handler Called\n");
return IRQ_HANDLED;
}
static irqreturn_t my_thread_fn(int irq, void *dev_id) {
printk(KERN_INFO "IRQ Thread Function Called\n");
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev) {
int irq;
int ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, my_irq_handler, my_thread_fn, IRQF_ONESHOT, "my_device", NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
return ret;
}
return 0;
}
static int my_remove(struct platform_device *pdev) {
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,mydevice", },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("IRQ Handling Example");
```
### 排查步骤
1. **硬件检查**
- 使用示波器等工具确认中断信号实际产生。
2. **中断控制器配置**
- 检查中断控制器的寄存器配置,确认未被屏蔽且已启用。
3. **内核配置检查**
- 确认内核配置文件中启用了相关的中断功能(如 CONFIG_GPIOLIB 等)。
4. **设备树或平台数据**
- 确认设备树或平台数据中的中断配置正确。
- 示例设备树配置:
```dts
my_device {
compatible = "myvendor,mydevice";
interrupt-parent = <&gpio>;
interrupts = <15 IRQ_TYPE_LEVEL_HIGH>;
};
```
5. **检查传递的中断标志**
- 确认 `devm_request_threaded_irq` 中使用的标志与硬件要求匹配。
6. **确认注册的中断号**
- 打印获取的中断号,确认其有效性。
- 使用 `printk` 确认 `platform_get_irq` 返回的中断号。
7. **检查中断屏蔽**
- 在驱动程序中明确启用中断:
```c
enable_irq(irq);
```
8. **内核日志**
- 使用 `dmesg` 命令检查内核日志中是否有与中断相关的消息,确认是否有错误或警告。
通过以上步骤逐步排查,可以帮助确定中断处理程序未被调用的具体原因,并进行修正以确保硬件中断能够被正确处理。
`complete`和`wait_for_completion_timeout`函数在Linux内核中用于同步机制,特别是线程间同步。它们涉及到`completion`结构体。以下是这些函数的详细描述:
### `complete`
`complete`函数用来通知等待的线程某个特定事件已经发生。
**原型:**
```c
void complete(struct completion *x);
```
**参数:**
- `x`:指向`struct completion`类型的指针。
**使用场景:**
当某个事件(比如I/O操作完成)发生后,调用`complete`函数来通知等待在这个事件上的线程。
**示例:**
```c
#include <linux/completion.h>
struct completion my_completion;
void async_function(void) {
// 执行一些异步操作...
// 通知事件已完成
complete(&my_completion);
}
int my_function(void) {
init_completion(&my_completion);
// 启动异步操作的线程
kthread_run(async_function, NULL, "async_thread");
// 等待事件完成
wait_for_completion(&my_completion);
// 事件已完成,继续执行
return 0;
}
```
### `wait_for_completion_timeout`
`wait_for_completion_timeout`函数用于等待一个特定事件完成或者超时。
**原型:**
```c
unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout);
```
**参数:**
- `x`:指向`struct completion`类型的指针。
- `timeout`:等待超时时间,以jiffies为单位。
**返回值:**
- 返回剩余的jiffies数。如果返回0,表示等待超时;否则表示事件在超时前完成,并返回剩余的jiffies数。
**使用场景:**
需要在线程间等待某个事件发生,但又不能无限制地等待。
**示例:**
```c
#include <linux/completion.h>
struct completion my_completion;
void async_function(void) {
// 执行一些异步操作...
// 通知事件已完成
complete(&my_completion);
}
int my_function(void) {
init_completion(&my_completion);
// 启动异步操作的线程
kthread_run(async_function, NULL, "async_thread");
// 等待事件完成或超时,超时时间设置为10个jiffies
if (wait_for_completion_timeout(&my_completion, 10) == 0) {
// 超时处理,事件未在超时前完成
printk(KERN_ERR "Wait timed out\n");
return -ETIMEDOUT;
}
// 事件已在超时前完成,继续执行
return 0;
}
```
### 重要注意事项
1. **初始化**:在使用`struct completion`之前,必须先对其进行初始化,可以使用`init_completion`或`DECLARE_COMPLETION`宏。
2. **多次使用**:`struct completion`在被一个等待者使用过后,如果需要再次使用,必须重新初始化。
3. **互斥**:`complete`和等待函数(如`wait_for_completion_timeout`)通常出现在不同的线程中,以实现线程间的同步。
这些函数在内核中用于实现复杂的同步机制,使得线程能够高效地等待和通知事件。
`set_bit(ABS_X, input_dev->evbit)` 和 `set_bit(EV_ABS, input_dev->evbit)` 是用于设置输入设备事件位图的两种不同用法。它们在语法上类似,但实际用途和语义上存在显著区别。
### `set_bit(EV_ABS, input_dev->evbit)`
`set_bit(EV_ABS, input_dev->evbit)` 用于设置输入设备支持的事件类型,这里设置的是绝对轴事件(Absolute Axis event)。
- **EV_ABS**:表示设备支持绝对位置(轴)事件,比如触摸屏或图形平板等设备,可以报告触摸点的具体位置数据(如X和Y轴坐标)。
在输入设备驱动中,你需要明确指明你的设备支持哪些类型的事件。例如,如果设备支持绝对位置事件,就需要设置 `EV_ABS` 位。
### `set_bit(ABS_X, input_dev->absbit)`
`set_bit(ABS_X, input_dev->absbit)` 用于设置输入设备在特定事件类型下支持的具体事件代码,这里设置的是支持绝对位置事件中的X轴事件。
- **ABS_X**:表示设备支持报告X轴的绝对位置数据。
在设置了事件类型 `EV_ABS` 之后,你还需要具体指明设备支持哪些绝对轴事件,例如X轴(ABS_X)、Y轴(ABS_Y)等等。
### 使用场景和区别
#### set_bit(EV_ABS, input_dev->evbit)
1. **目的**:声明设备支持绝对轴事件类型。
2. **使用场景**:在初始化输入设备时,设置这个位表明设备可以生成绝对轴事件,例如触摸屏、图形平板等。
#### set_bit(ABS_X, input_dev->absbit)
1. **目的**:声明设备支持绝对轴事件类型中的特定事件(如X轴)。
2. **使用场景**:在设备支持绝对轴事件类型(已经设置 `EV_ABS`)之后,进一步设置具体的绝对轴事件代码,例如X轴(ABS_X)、Y轴(ABS_Y)、触摸压力(ABS_PRESSURE)等。
### 综合示例
为了更好地理解这两个操作符的区别,以下是一个综合的示例,展示如何在一个输入设备驱动中使用它们:
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
static struct input_dev *input_dev;
static int __init input_dev_init(void)
{
int error;
// 分配并初始化输入设备
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Failed to allocate input device\n");
return -ENOMEM;
}
input_dev->name = "Example Input Device";
input_dev->phys = "example/input0";
input_dev->id.bustype = BUS_VIRTUAL;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
// 设置设备支持的事件类型
set_bit(EV_ABS, input_dev->evbit); // 支持绝对轴事件类型
set_bit(EV_KEY, input_dev->evbit); // 支持按键事件
// 设置支持的绝对轴事件和其范围
set_bit(ABS_X, input_dev->absbit); // 支持X轴绝对位置事件
set_bit(ABS_Y, input_dev->absbit); // 支持Y轴绝对位置事件
input_set_abs_params(input_dev, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 768, 0, 0);
// 设置支持的按键事件
set_bit(KEY_A, input_dev->keybit); // 支持按键 'A'
// 注册输入设备
error = input_register_device(input_dev);
if (error) {
printk(KERN_ERR "Failed to register input device\n");
input_free_device(input_dev);
return error;
}
printk(KERN_INFO "Example input device registered successfully\n");
return 0;
}
static void __exit input_dev_exit(void)
{
input_unregister_device(input_dev);
input_free_device(input_dev);
printk(KERN_INFO "Example input device unregistered successfully\n");
}
module_init(input_dev_init);
module_exit(input_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example Input Device Driver");
```
### 解释
1. **分配和初始化输入设备**:
```c
input_dev = input_allocate_device();
```
2. **设置设备属性**:
```c
input_dev->name = "Example Input Device";
```
3. **设置事件类型和具体事件代码**:
```c
set_bit(EV_ABS, input_dev->evbit); // 支持绝对轴事件类型
set_bit(EV_KEY, input_dev->evbit); // 支持按键事件
set_bit(ABS_X, input_dev->absbit); // 支持X轴绝对位置事件
set_bit(ABS_Y, input_dev->absbit); // 支持Y轴绝对位置事件
input_set_abs_params(input_dev, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 768, 0, 0);
set_bit(KEY_A, input_dev->keybit); // 支持按键 'A'
```
4. **注册输入设备**:
```c
error = input_register_device(input_dev);
```
### 总结
- `set_bit(EV_ABS, input_dev->evbit)` 用于声明设备支持绝对轴事件类型(如触摸屏)。
- `set_bit(ABS_X, input_dev->absbit)` 用于在声明支持绝对轴事件类型之后,进一步声明设备支持的具体绝对轴事件代码(如X轴位置)。
这两个操作符通常一起使用,首先声明设备支持的事件类型(如 `EV_ABS`),接着声明具体的事件代码(如 `ABS_X`)。这样可以清晰地描述设备的能力和功能。
调用 `__set_bit(EV_ABS, input_dev->evbit);` 只是启用输入设备支持绝对轴事件类型(`EV_ABS`),但是它不会自动启用 `ABS_X` 和 `ABS_Y` 等具体的绝对轴事件代码。在 Linux 内核中,你需要显式地设置每个具体的绝对轴事件代码,以指明输入设备支持这些事件。
### 详细解释
#### 1. 启用事件类型
通过 `__set_bit(EV_ABS, input_dev->evbit);`,你告诉内核此输入设备将生成绝对轴事件,但这只是声明设备支持的事件类型,类似于一个大类。
```c
__set_bit(EV_ABS, input_dev->evbit);
```
#### 2. 启用具体的绝对轴事件代码
要具体说明输入设备支持哪些绝对轴事件(如 X 轴和 Y 轴),必须分别设置这些事件代码。例如:
```c
__set_bit(ABS_X, input_dev->absbit); // 启用 X 轴绝对位置事件
__set_bit(ABS_Y, input_dev->absbit); // 启用 Y 轴绝对位置事件
```
此外,还需要使用 `input_set_abs_params` 函数来设置这些轴的参数(例如轴的最小值、最大值等),否则即使启用了这些位,也没有为它们设置合理的参数。
### 整体示例
以下是一个完整的示例代码,展示如何设置和启用一个支持绝对轴事件(X 轴和 Y 轴)的输入设备:
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
static struct input_dev *input_dev;
static int __init input_dev_init(void)
{
int error;
// 分配并初始化输入设备
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Failed to allocate input device\n");
return -ENOMEM;
}
input_dev->name = "Example Input Device";
input_dev->phys = "example/input0";
input_dev->id.bustype = BUS_VIRTUAL;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
// 启用设备支持的事件类型
__set_bit(EV_ABS, input_dev->evbit);
// 启用具体的绝对轴事件代码和其范围
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
input_set_abs_params(input_dev, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 768, 0, 0);
// 注册输入设备
error = input_register_device(input_dev);
if (error) {
printk(KERN_ERR "Failed to register input device\n");
input_free_device(input_dev);
return error;
}
printk(KERN_INFO "Example input device registered successfully\n");
return 0;
}
static void __exit input_dev_exit(void)
{
input_unregister_device(input_dev);
input_free_device(input_dev);
printk(KERN_INFO "Example input device unregistered successfully\n");
}
module_init(input_dev_init);
module_exit(input_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example Input Device Driver");
```
### 解释
1. **分配和初始化输入设备**:
```c
input_dev = input_allocate_device();
```
2. **设置设备属性**:
```c
input_dev->name = "Example Input Device";
input_dev->phys = "example/input0";
input_dev->id.bustype = BUS_VIRTUAL;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
```
3. **启用事件类型**:
```c
__set_bit(EV_ABS, input_dev->evbit); // 启用绝对轴事件类型
```
4. **启用具体的绝对轴事件代码和设置参数**:
```c
__set_bit(ABS_X, input_dev->absbit); // 启用 X 轴绝对位置事件
__set_bit(ABS_Y, input_dev->absbit); // 启用 Y 轴绝对位置事件
input_set_abs_params(input_dev, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 768, 0, 0);
```
5. **注册输入设备**:
```c
error = input_register_device(input_dev);
```
### 总结
`__set_bit(EV_ABS, input_dev->evbit);` 只是启用了绝对轴事件类型,但不会自动启用 `ABS_X` 和 `ABS_Y` 等具体的绝对轴事件代码。你需要显式地设置每个具体的事件代码,并使用 `input_set_abs_params` 函数来配置这些轴的参数,以确保输入设备能够正确生成和报告这些事件。
调用 `input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts_bdata->panel_max_x, 0, 0);` 并不会自动启用 `ABS_X` 和 `ABS_Y` 的位图。你仍然需要显式地启用这些位图。
### 详细解释
#### `input_set_abs_params` 函数
`input_set_abs_params` 是一个方便的函数,用于设置给定绝对轴事件的参数。这些参数包括最小值、最大值、分辨率等等。然而,这个函数并不会自动设置相关事件类型的位图。
### 启用绝对轴事件
为了使输入设备支持绝对轴事件,你需要:
1. 启用绝对轴事件类型(`EV_ABS`)。
2. 启用具体的绝对轴事件代码(如 `ABS_X`, `ABS_Y`)。
3. 设置这些轴的参数。
### 整合示例
以下是一个完整的示例,展示如何显式地启用 `EV_ABS` 事件类型和具体的绝对轴事件代码:
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
static struct input_dev *input_dev;
static int __init input_dev_init(void)
{
int error;
int max_x = 1024; // 假设的屏幕最大X值
int max_y = 768; // 假设的屏幕最大Y值
// 分配并初始化输入设备
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Failed to allocate input device\n");
return -ENOMEM;
}
input_dev->name = "Example Input Device";
input_dev->phys = "example/input0";
input_dev->id.bustype = BUS_VIRTUAL;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
// 启用设备支持的事件类型
__set_bit(EV_ABS, input_dev->evbit);
// 启用具体的绝对轴事件代码
__set_bit(ABS_MT_POSITION_X, input_dev->absbit);
__set_bit(ABS_MT_POSITION_Y, input_dev->absbit);
// 设置具体的轴参数
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, max_x, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
// 注册输入设备
error = input_register_device(input_dev);
if (error) {
printk(KERN_ERR "Failed to register input device\n");
input_free_device(input_dev);
return error;
}
printk(KERN_INFO "Example input device registered successfully\n");
return 0;
}
static void __exit input_dev_exit(void)
{
input_unregister_device(input_dev);
input_free_device(input_dev);
printk(KERN_INFO "Example input device unregistered successfully\n");
}
module_init(input_dev_init);
module_exit(input_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example Input Device Driver");
```
### 解释
1. **分配和初始化输入设备**:
```c
input_dev = input_allocate_device();
```
2. **设置设备属性**:
```c
input_dev->name = "Example Input Device";
```
3. **启用事件类型**:
```c
__set_bit(EV_ABS, input_dev->evbit); // 启用绝对轴事件类型
```
4. **启用具体的绝对轴事件代码**:
```c
__set_bit(ABS_MT_POSITION_X, input_dev->absbit); // 启用X轴绝对位置事件
__set_bit(ABS_MT_POSITION_Y, input_dev->absbit); // 启用Y轴绝对位置事件
```
5. **设置具体的轴参数**:
```c
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, max_x, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
```
6. **注册输入设备**:
```c
error = input_register_device(input_dev);
```
### 总结
虽然 `input_set_abs_params` 用于设置特定轴的参数,但它不会自动启用相关的绝对轴事件代码位图。你需要显式地启用这些位图,以确保输入设备能够正确地生成和报告这些事件。因此,完整的设置流程包括:
1. 启用事件类型位图(如 `EV_ABS`)。
2. 启用具体的绝对轴事件代码位图(如 `ABS_X`, `ABS_Y`)。
3. 设置这些绝对轴的参数(如最小值、最大值、分辨率等)。
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
为何只设置这些bitmap就可以正常报点?
在Linux输入子系统中,设置位图和参数是告知系统你的输入设备支持哪些特定的事件和属性。下面详细解释为什么这些设置足够使输入设备正常工作和报告事件。
### 设置解释
1. **启用事件类型**:
```c
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
```
- `EV_ABS`:启用绝对轴事件类型,用于报告触摸位置等绝对坐标。
- `EV_KEY`:启用按键事件类型,用于报告按键状态。
2. **启用具体按键事件代码**:
```c
__set_bit(BTN_TOUCH, input_dev->keybit);
```
- `BTN_TOUCH`:表示触摸事件的按键代码,通常用于表示触摸开始或结束的状态。
3. **设置绝对轴事件参数**:
```c
input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
```
- `ABS_X` 和 `ABS_Y`:设置X轴和Y轴的范围和参数。
- 范围从0到4095,表示触摸屏可以在这两个方向的最大分辨率。
- 这里的5表示 "fuzz",即容差(可能的小变动忽略范围),0表示没有附加参数。
### 正常报点的原因
1. **设置事件类型**:
当你设置了 `EV_ABS` 和 `EV_KEY` 事件类型,这就告诉内核和用户空间应用程序该设备将报告绝对值事件和按键事件。这让系统准备好接收和处理这些类型的事件。
2. **设置具体事件代码**:
通过设置 `BTN_TOUCH`,你启用了触摸按键事件,这用于报告触摸动作的开始和结束。例如,一个简单的触摸屏设备可以通过报告 `BTN_TOUCH` 的按压(press)和释放(release)事件来表示触摸开始和结束。
3. **设置绝对轴参数**:
`ABS_X` 和 `ABS_Y` 的设置确定了触摸点的位置范围和精度。这使得设备能够报告触摸点的具体位置,用户空间应用程序可以通过读取这些值来得到触摸的位置。
### 样例代码总结
以下的设置足够使输入设备能够正常工作并报告事件:
```c
__set_bit(EV_ABS, input_dev->evbit); // 启用绝对轴事件类型
__set_bit(EV_KEY, input_dev->evbit); // 启用按键事件类型
__set_bit(BTN_TOUCH, input_dev->keybit); // 启用触摸按钮事件
// 设置X轴和Y轴的参数
input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
```
### 触摸事件的报告
在实际运行时,驱动程序通常会根据硬件的输入数据来报告触摸事件。例如,驱动程序在中断处理中读取触摸屏控制器的数据,并通过如下代码报告触摸事件:
```c
void report_touch_event(struct input_dev *dev, int x, int y, bool touch)
{
input_report_key(dev, BTN_TOUCH, touch); // 报告触摸按钮状态(按下或松开)
input_report_abs(dev, ABS_X, x); // 报告X轴位置
input_report_abs(dev, ABS_Y, y); // 报告Y轴位置
input_sync(dev); // 同步事件
}
```
在这个例子中:
- `input_report_key(dev, BTN_TOUCH, touch)`:报告触摸按钮的状态。如果 `touch` 为 `true`,表示触摸开始;如果为 `false`,表示触摸结束。
- `input_report_abs(dev, ABS_X, x)` 和 `input_report_abs(dev, ABS_Y, y)`:报告触摸点的X轴和Y轴位置。
- `input_sync(dev)`:同步事件,确保事件被正确提交。
### 总结
通过设置 `EV_ABS` 和 `EV_KEY` 事件类型,并显式启用特定的事件代码(如 `BTN_TOUCH`)和绝对轴参数(如 `ABS_X` 和 `ABS_Y`),输入设备能够正常报告触摸事件。上述设置足够详细,使得输入子系统和用户空间应用程序能够正确处理和解释这些事件数据。这种设置方法非常适合用于简单触摸屏设备的基本驱动开发。
unsigned char psd_ub98x_hy11_subdev_init_reg [][6] = {
// addr, reg, value, rbdelay, retry, delay(defore)
{DS90UH983_ADDR, 0x72, (ADS1000_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x7a, (ADS1000_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x8a, 0x00, 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x73, (GPIO_TCA9539_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x7b, (GPIO_TCA9539_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x8b, 0x00, 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x74, (TP_CYAT817A_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x7c, (TP_CYAT817A_ADDR<<1), 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x8c, 0x00, 0x01, 0x05, 0x00 },
{DS90UH983_ADDR, 0x75, (AOXIAN_TCON_DEVICE_ADDR<<1), 0x01, 0x05, 0x00 }, //0x75 - SLAVE_ID_5
{DS90UH983_ADDR, 0x7d, (AOXIAN_TCON_DEVICE_ADDR<<1), 0x01, 0x05, 0x00 }, //0x7D - SLAVE_ALIAS_5
{DS90UH983_ADDR, 0x8d, 0x00, 0x01, 0x05, 0x00 }, //0x8D - SLAVE_DEST_5
};
unsigned char psd_ub98x_hy11_dev_init_reg [][6] = {
//{addr,reg, value, rbdelay, retry, delay(defore)},
{DS90UH983_ADDR, 0x2d, 0x13, 0x01, 0x05, 0x00 }, //TX_PORT_SEL TX_WRITE_PORT_0 TX_WRITE_PORT_1
{DS90UH984_ADDR, 0x12, 0x00, 0x01, 0x05, 0x00 }, //GPIO IN CLEAN
{DS90UH984_ADDR, 0x13, 0x00, 0x01, 0x05, 0x00 }, //GPIO IN CLEAN
{0x00, 0x00, 0x00, 0x00, 0x00, 0x14 }, //delay 20ms
{DS90UH984_ADDR, 0x17, 0xc1, 0x01, 0x05, 0x0a }, //LCD3V3_EN GPIO2
{0x00, 0x00, 0x00, 0x00, 0x00, 0x14 }, //delay 20ms
{DS90UH984_ADDR, 0x18, 0xc1, 0x01, 0x05, 0x0a }, //LCD_RST GPIO3
{0x00, 0x00, 0x00, 0x00, 0x00, 0x14 }, //delay 20ms
{DS90UH984_ADDR, 0x1A, 0xc1, 0x01, 0x05, 0x0a }, //TOUCH_EN GPIO5
{0x00, 0x00, 0x00, 0x00, 0x00, 0x14 }, //delay 20ms
{DS90UH984_ADDR, 0x1b, 0xc1, 0x01, 0x05, 0x0a }, //TP_RST GPIO6
/*Send 983 GPIO0 to 984 GPIO0 begin*/
{DS90UH984_ADDR, 0x0E, 0x01, 0x01, 0x05, 0x00 }, //984 Select Port 0
{DS90UH983_ADDR, 0x0D, 0x01, 0x01, 0x05, 0x00 }, //983 Enable 1x FC GPIO slots
{DS90UH983_ADDR, 0x15, 0x10, 0x01, 0x05, 0x00 }, //983 Send GPIO0 to FC GPIO slot 0, Send GPIO1 to FC GPIO slot 1
{DS90UH984_ADDR, 0x15, 0x94, 0x01, 0x05, 0x00 }, //Send 983 GPIO0 to 984 GPIO0
{DS90UH984_ADDR, 0x13, 0xBC, 0x01, 0x05, 0x00 }, //Set 984 inputs/outputs Outputs - GPIO0,GPIO1,GPIO6,GPIO9
/*Send 983 GPIO0 to 984 GPIO0 end*/
{DS90UH984_ADDR, 0x12, 0x03, 0x01, 0x05, 0x00 }, //0x12 - GPIO_IN_EN1, GPIO8_INPUT_EN
{DS90UH984_ADDR, 0x1C, 0x00, 0x01, 0x05, 0x00 }, //0x1C - GPIO7_PIN_CTL=0x00, Output Disable
{DS90UH984_ADDR, 0x1D, 0x00, 0x01, 0x05, 0x00 }, //0x1D - GPIO8_PIN_CTL=0x00, Output Disable
{DS90UH984_ADDR, 0x1E, 0x00, 0x01, 0x05, 0x00 }, //0x1E - GPIO9_PIN_CTL=0x00, Output Disable
};
unsigned char psd_ub98x_hy11_TP_INT_FPD3_reg [][6] = {
/********************************************************************
map TP_INT to Soc [begin]:
TP --> 984(INTB_IN) --> 983(REM_INTB/GPIO4) --> Soc
********************************************************************/
{DS90UH984_ADDR, 0x44, 0x81, 0x01, 0x05, 0x14 }, //Reg = 0,0x0044,0x81 RX_INTN_CTL: IE_RX_INTN_INT_P0, IE_RX_INTN_INTB_P0
{DS90UH983_ADDR, 0xc6, 0x21, 0x01, 0x05, 0x00 }, //Reg = 0,0x00C6,0x21 FPD3_ICR: IE_RX_REM_INT, INT_EN
{DS90UH983_ADDR, 0x1b, 0x88, 0x01, 0x05, 0x00 }, //Reg = 0,0x001B,0x88 GPIO4_PIN_CTL: GPIO4_OUTPUT_EN, GPIO4_OUT_SEL="PORT0 RX_INTN"
{DS90UH983_ADDR, 0x51, 0x83, 0x01, 0x05, 0x00 }, //Reg = 0,0x0051,0x83 INTERRUPT_CTL: INTB_PIN_EN, IE_FPD_TX0, IE_FPD_TX1
/* map TP_INT to Soc [end] */
};
unsigned char psd_ub98x_hy11_TP_INT_FPD4_reg [][6] = {
/********************************************************************
map TP_INT to Soc [begin]:
TP --> 984(INTB_IN) --> 983(REM_INTB/GPIO4) --> Soc
********************************************************************/
{DS90UH984_ADDR, 0x44, 0x81, 0x01, 0x05, 0x14 }, //Reg = 0,0x0044,0x81 RX_INTN_CTL: IE_RX_INTN_INT_P0, IE_RX_INTN_INTB_P0
{DS90UH983_ADDR, 0x40, 0x24, 0x01, 0x05, 0x00 }, //PAGE 9: DP DFT Registers
{DS90UH983_ADDR, 0x41, 0x8C, 0x01, 0x05, 0x00 }, //INTR_CTL_FPD4_PORT0 Register (Address = 0x8C)
{DS90UH983_ADDR, 0x42, 0x20, 0x01, 0x05, 0x00 }, //IE_RX_REM_INT
{DS90UH983_ADDR, 0x1b, 0x88, 0x01, 0x05, 0x00 }, //Reg = 0,0x001B,0x88 GPIO4_PIN_CTL: GPIO4_OUTPUT_EN, GPIO4_OUT_SEL="PORT0 RX_INTN"
{DS90UH983_ADDR, 0x51, 0x83, 0x01, 0x05, 0x00 }, //Reg = 0,0x0051,0x83 INTERRUPT_CTL: INTB_PIN_EN, IE_FPD_TX0, IE_FPD_TX1
/* map TP_INT to Soc [end] */
};
int ub98x_hy11_psd_write_init_array_reg_common(struct i2c_client *client)
{
unsigned short addr_store = 0;
int ret = 0;
int retry_time = 2;
while ((ret = psd_ub98x_reg_array_write(client, psd_ub98x_hy11_subdev_init_reg, ARRAY_SIZE(psd_ub98x_hy11_subdev_init_reg))) < 0 && retry_time > 0) {
LOG_I("%s psd_ub98x_hy11_subdev_init_reg retry! ret:%d ,time:%d\n",__FUNCTION__,ret ,--retry_time);
}
if (ret < 0) {
LOG_I("%s psd_ub98x_hy11_subdev_init_reg fail \n",__FUNCTION__);
return -1;
}
LOG_I("%s step 4 psd_ub98x_hy11_dev_init_reg\n",__FUNCTION__);
retry_time = 2;
while ((ret = psd_ub98x_reg_array_write(client, psd_ub98x_hy11_dev_init_reg, ARRAY_SIZE(psd_ub98x_hy11_dev_init_reg))) < 0 && retry_time > 0) {
LOG_I("%s psd_ub98x_hy11_dev_init_reg retry! ret:%d ,time:%d\n",__FUNCTION__,ret ,--retry_time);
}
if (ret < 0) {
LOG_I("%s psd_ub98x_hy11_dev_init_reg fail \n",__FUNCTION__);
return -1;
}
return 0;
}
int ub98x_hy11_psd_write_init_array_reg(struct i2c_client *client)
{
unsigned short addr_store = 0;
int ret = 0;
int retry_time = 2;
ret = ub98x_hy11_psd_write_init_array_reg_common(client);
if (ret < 0) {
return -1;
}
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD4_reg\n",__FUNCTION__);
while ((ret = psd_ub98x_reg_array_write(client, psd_ub98x_hy11_TP_INT_FPD4_reg, ARRAY_SIZE(psd_ub98x_hy11_TP_INT_FPD4_reg))) < 0 && retry_time > 0) {
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD4_reg retry! ret:%d ,time:%d\n",__FUNCTION__,ret ,--retry_time);
}
if (ret < 0) {
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD4_reg fail \n",__FUNCTION__);
return -1;
}
return 0;
}
int ub98x_hy11_psd_write_init_array_reg_fpd3(struct i2c_client *client)
{
unsigned short addr_store = 0;
int ret = 0;
int retry_time = 2;
ret = ub98x_hy11_psd_write_init_array_reg_common(client);
if (ret < 0) {
return -1;
}
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD3_reg\n",__FUNCTION__);
while ((ret = psd_ub98x_reg_array_write(client, psd_ub98x_hy11_TP_INT_FPD3_reg, ARRAY_SIZE(psd_ub98x_hy11_TP_INT_FPD3_reg))) < 0 && retry_time > 0) {
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD3_reg retry! ret:%d ,time:%d\n",__FUNCTION__,ret ,--retry_time);
}
if (ret < 0) {
LOG_I("%s psd_ub98x_hy11_TP_INT_FPD3_reg fail \n",__FUNCTION__);
return -1;
}
return 0;
}
634

被折叠的 条评论
为什么被折叠?



