【HID键盘原理全解】:10分钟内掌握电气特性与通信协议
发布时间: 2024-12-13 17:19:41 阅读量: 25 订阅数: 16
java USBHID通信协议
![【HID键盘原理全解】:10分钟内掌握电气特性与通信协议](https://jmgsecurity.com/wp-content/uploads/2018/10/hid-logo.png)
参考资源链接:[USB HID到PS/2键盘键码转换详表](https://wenku.csdn.net/doc/6412b7adbe7fbd1778d4b278?spm=1055.2635.3001.10343)
# 1. HID键盘的概述与工作原理
## 1.1 HID键盘的定义
HID键盘(Human Interface Device)是一种标准的输入设备,广泛用于计算机和其他电子设备中。HID键盘的通信协议基于USB(Universal Serial Bus)标准,具有即插即用和热插拔的特点。
## 1.2 HID键盘的工作原理
HID键盘通过USB接口与计算机连接,键盘内部的微控制器将按键操作转换为HID报告,然后通过USB接口传输到计算机。计算机接收到HID报告后,通过HID驱动程序解析,将按键操作转化为用户输入。
## 1.3 HID键盘的优势
HID键盘的优势在于其高度的兼容性和易用性。由于遵循USB标准,HID键盘可以在不同的操作系统上使用,无需安装额外的驱动程序。同时,HID键盘还支持热插拔,可以在计算机运行过程中随时连接或断开,使用非常方便。
# 2. HID键盘的电气特性分析
HID键盘作为计算机输入设备的重要组成部分,其电气特性直接决定了键盘的性能和兼容性。接下来将详细探讨HID键盘的基本电气特性以及信号传输特性。
### 2.1 基本电气特性
HID键盘的电气特性主要包括了电压和电流要求,以及接口类型和引脚定义。
#### 2.1.1 电压和电流要求
HID键盘通常在2.8V至5.5V的电压范围内工作,而USB接口能提供的电压为5V。在设计HID键盘时,应考虑到微控制器和其他电子元件的电压兼容性问题。例如,一些微控制器能够在1.8V至5.5V的范围内正常工作,这样的电压灵活性使得它们成为HID键盘设计的理想选择。
电流消耗方面,HID键盘的空闲状态电流通常非常低,当没有按键活动时,流过键盘的电流可能只有几微安。然而,按键被按下时,电流会根据电路设计的不同而有所增加。在设计时,需要确保键盘电路能够从USB接口获取足够的电流供给,同时不超过USB接口的电流限制(通常为500mA)。
在设计HID键盘电路时,设计师应遵循这些电气规范来保证键盘的可靠工作。为了进一步优化能效,可以采用低功耗设计和睡眠模式,在不活动时段降低能耗。
```mermaid
flowchart LR
A[开始] --> B[确定电源电压]
B --> C[选择合适的微控制器]
C --> D[电流限制分析]
D --> E[设计低功耗电路]
E --> F[实施睡眠模式策略]
F --> G[完成电气特性设计]
```
#### 2.1.2 接口类型和引脚定义
HID键盘普遍采用USB作为接口标准,通过USB A型或USB Mini/Micro接口与计算机连接。USB接口的引脚定义遵循标准的USB规范,包括VCC(电源线)、GND(地线)、D+(数据+)和D-(数据-)等。
引脚的功能定义对于设计至关重要。比如,D+和D-线在USB 2.0高速模式下用来传输差分信号,这就要求键盘内部电路能够支持这种差分信号的传输。而VCC和GND引脚是电源管理的关键,需确保电压稳定且噪声控制在允许范围内。
### 2.2 信号传输特性
HID键盘的数据传输涉及到信号编码方式以及信号传输速率和同步两个方面。
#### 2.2.1 信号编码方式
信号编码是指将按键信号转换为计算机能够理解的数据格式。HID键盘使用的信号编码方式以HID报告描述符为核心,它定义了每种按键动作对应的数据编码。例如,按下"A"键时,可能会发出一个特定的代码,如"04 00 00 00",这个代码的每个部分都有特定的含义,包括按键状态和按键位置等。
编码过程中,还需考虑消抖逻辑,确保按键输入不会因为电子元件或物理接触的波动而产生多次误报。设计时通常会在硬件或固件中加入一些简单的延时逻辑或软件滤波算法,以确保信号的准确性。
```markdown
| 按键 | 编码 |
| ---- | ---- |
| A | 04 00 00 00 |
| B | 05 00 00 00 |
| ... | ... |
```
#### 2.2.2 信号传输速率和同步
HID键盘的数据传输速率受到USB接口速率的限制,USB 2.0全速模式下,数据传输速率为12 Mbps。为了保证信号的同步,HID设备使用端点来区分不同类型的数据传输,如控制端点、中断端点和批量端点等。
在设计时,需合理分配端点类型,考虑到传输速率和同步的需求,确保快速和频繁的按键动作(例如打字)能够得到及时和准确的响应。例如,使用中断端点传输按键数据,能够保证数据在规定的时间内被系统读取,避免了同步问题。
USB HID键盘的数据传输采用批量传输方式,以固定的数据包大小进行。每个数据包都含有时间戳、按键状态和按键数量等信息,确保了数据传输的同步性。这种传输方式不仅保证了数据的准确传输,也利于操作系统处理多个设备的输入。
```mermaid
sequenceDiagram
participant K as HID Keyboard
participant C as Computer
K->>C: Send Report Packet
Note right of C: Decode Packet
C->>K: Acknowledge Receipt
```
以上对HID键盘的电气特性分析,为其硬件设计和固件编程提供了理论基础。了解这些基本的电气特性和信号传输特性,对于实现一个性能稳定、兼容性良好的HID键盘至关重要。
# 3. HID键盘通信协议详解
## 3.1 USB HID协议基础
### 3.1.1 HID类协议的定义
USB HID(Human Interface Device)类协议是USB(Universal Serial Bus)通信协议中的一部分,专门用于描述和传输人机交互设备(如键盘、鼠标等)的数据。HID类协议作为USB标准化的一部分,它定义了设备与主机间交换数据的标准方式,确保了不同制造商生产的设备能够在不同操作系统下兼容工作。
HID设备通过一系列的报告来与主机通信,报告是通过HID类定义的数据包格式进行传递的。这些报告描述了设备的状态,例如键盘按键是否被按下,或者鼠标移动的距离。通过这种方式,HID协议支持即时输入,允许计算机响应键盘和鼠标事件,而无需等待设备数据的轮询。
### 3.1.2 报告描述符的结构与解析
报告描述符是HID设备与主机通信的关键部分,它包含了关于HID设备如何与主机交换数据的详细信息。报告描述符是一系列的字段,每个字段都定义了设备的一个特性,例如按钮的数量、指示灯的状态或者设备的报告ID。
报告描述符使用HID规范定义的特定编码格式来描述,包含了一些特殊用途的字段,如使用Usage Page来定义设备的类别(例如键盘、鼠标等),以及Usage来指定设备的具体功能。一个典型的HID报告描述符可以描述多个不同的输入(按键)、输出(指示灯)和特征(如设备的电源状态)。
解析报告描述符是编写HID驱动程序或应用程序时必须要处理的复杂任务。软件开发者需要使用专门的解析工具或编写自定义的解析代码,以便准确地理解HID设备发送的数据,并将这些数据转换成用户的输入指令。
## 3.2 协议层通信过程
### 3.2.1 初始化与设备枚举过程
当HID设备连接到计算机时,它会通过USB总线发起与主机的通信。通信开始的第一步是设备的初始化和枚举过程。此过程包括设备的识别、配置以及与主机建立通信链路。主机通过一系列的控制传输请求获取设备的报告描述符,随后,设备会报告其支持的特定HID功能。
在枚举过程中,主机将为设备分配一个唯一的地址,并且确定了数据交换的通道。此外,主机还会查询设备的能力,包括其支持的最大包大小、设备支持的报告类型以及设备的HID协议版本等。
### 3.2.2 数据交换和设备控制命令
在初始化和枚举完成后,HID设备开始与主机进行周期性的数据交换。数据交换是通过中断传输实现的,即设备在特定的事件(如按键操作)发生时向主机发送数据。设备周期性地向主机发送包含按键状态、移动坐标等信息的数据包。
除了正常的数据交换,HID协议还支持主机向设备发送控制命令。控制命令通常用于改变设备的配置或者获取设备的状态信息。例如,主机可以发送一个命令来设置设备的报告率,或者请求设备提供特定的传感器数据。
### 3.2.3 设备与主机交互的协议图解
下图展示了一个简化的HID设备与主机交互的流程,包括初始化、枚举、数据交换和控制命令的环节。
```mermaid
graph LR
A[连接HID设备] --> B[初始化过程]
B --> C[枚举过程]
C --> D{设备是否已枚举?}
D -->|是| E[数据交换]
D -->|否| B
E --> F[控制命令交互]
```
### 3.2.4 代码示例与分析
以下是一个简化的代码示例,演示了如何使用Linux下的`libusb`库来枚举USB HID设备,并从设备读取数据。
```c
#include <stdio.h>
#include <libusb-1.0/libusb.h>
int main() {
libusb_device_handle *dev_handle;
libusb_device **devs;
libusb_context *ctx = NULL;
int r, i = 0;
ssize_t dev_count;
r = libusb_init(&ctx);
if (r < 0) {
fprintf(stderr, "初始化失败: %s\n", libusb_error_name(r));
return 1;
}
dev_count = libusb_get_device_list(ctx, &devs);
if (dev_count < 0) {
fprintf(stderr, "获取设备列表失败\n");
return 1;
}
while ((devs[i] != NULL)) {
struct libusb_device_descriptor desc;
r = libusb_get_device_descriptor(devs[i], &desc);
if (r < 0) {
fprintf(stderr, "获取设备描述符失败\n");
continue;
}
// 检查设备是否为HID类设备
if (desc.idVendor == 0x1234 && desc.idProduct == 0x5678) {
// 打开设备
r = libusb_open(devs[i], &dev_handle);
if (r < 0) {
fprintf(stderr, "无法打开设备\n");
break;
}
// ... 进行数据交换操作 ...
libusb_close(dev_handle);
break;
}
i++;
}
libusb_free_device_list(devs, 1);
libusb_exit(ctx);
return 0;
}
```
上述代码首先初始化了libusb库,然后获取USB设备列表,并遍历设备列表来寻找特定的HID设备。在此示例中,我们假设要枚举的设备具有特定的`idVendor`和`idProduct`。当找到匹配的设备后,代码尝试打开设备,并准备进行数据交换。需要注意的是,在实际使用中,还需要处理设备的热插拔事件和正确的错误处理逻辑。
通过此代码,我们可以看到HID设备枚举和通信过程的基本原理。在实际应用中,可能需要进一步处理设备的初始化、配置、数据读取和关闭等环节,并且要确保代码能够正确地处理各种异常情况。
# 4. HID键盘的硬件设计要点
硬件设计是HID键盘实现其功能的核心部分,涵盖了从选择合适微控制器到电路设计的各个层面。本章节将深入探讨硬件设计中的关键要素,为工程师提供设计高性能HID键盘的实用指导。
## 4.1 微控制器与HID接口
### 4.1.1 微控制器选择与配置
选择一个合适的微控制器(MCU)是设计HID键盘的基础。MCU需要具备以下几点特性:
- 支持USB通信协议,至少有一个USB全速或高速硬件控制器。
- 拥有足够的I/O端口用于矩阵键盘的行列扫描。
- 具备一定容量的内存,用于存储固件和键盘映射表。
- 可以提供稳定的时钟源,保证按键扫描的精确性。
在配置MCU时,需要对USB相关的硬件进行初始化,包括配置USB端点、设置中断处理和初始化相关的寄存器。此外,还需要设置必要的I/O端口为输入或输出模式,以适应矩阵键盘的行列扫描。
```c
// 示例代码段:初始化USB模块和端口方向
USB_Init(); // 初始化USB模块
PORTD = 0x00; // 将端口D配置为输入
PORTE |= 0xFF; // 将端口E配置为输出
for (int row = 0; row < ROWS; row++) {
DDRE |= 1 << row; // 设置行端口为输出
}
for (int col = 0; col < COLS; col++) {
DDRD |= 1 << col; // 设置列端口为输入
}
```
### 4.1.2 HID固件编程基础
HID键盘的固件负责实现与宿主机的通信。编写固件时,需要实现以下几个关键功能:
- 构造HID报告描述符,描述键盘的按键和使用模式。
- 实现按键扫描算法,以检测按键的按下和释放事件。
- 编写报告生成函数,将按键事件转换为符合HID类规范的报告格式。
在编写报告描述符时,需要细致地定义键盘的布局和特殊按键功能。这通常通过一串XML格式的描述符定义实现,然后转换为二进制格式烧录到MCU中。
```c
// 示例代码段:定义HID报告描述符(简化版)
const uint8_t HID_ReportDescriptor[] __attribute__ ((aligned (4))) = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
// ... 其他按键和描述符的定义 ...
0xc0 // END_COLLECTION
};
// 示例代码段:按键扫描函数(简化版)
void ScanKeyboardMatrix() {
for (int row = 0; row < ROWS; row++) {
// 设置当前行激活
PORTE &= ~(1 << row);
for (int col = 0; col < COLS; col++) {
if ((PIND & (1 << col)) == 0) {
// 检测到按键按下
ReportKeyPress(row * COLS + col);
}
}
PORTE |= (1 << row); // 关闭当前行
}
}
```
## 4.2 硬件电路设计
### 4.2.1 电源管理设计
电源管理是确保键盘长时间稳定运行的关键。对于HID键盘而言,常见的设计方案是通过USB线供电,并在电路中设计稳压和滤波电路以保证电源的稳定性。此外,还需考虑USB suspend模式下的低功耗设计,以满足现代计算机的省电需求。
设计稳压电路时,可以使用低压差线性稳压器(LDO)或开关型稳压器(DC-DC converter),根据电路对噪声敏感程度和所需的效率来选择。电路设计还需包括必要的去耦电容和稳压器旁路电容,以确保电流供给的稳定。
### 4.2.2 信号完整性和噪声控制
信号完整性和噪声控制是电路设计中不可忽视的环节。HID键盘的信号线需要抗干扰能力强,因为按键事件是通过信号线传输至MCU,并最终发送至主机。通常采用以下措施来提高信号的抗干扰能力:
- 使用双绞线或屏蔽线缆来减少电磁干扰。
- 在电路板设计中,避免高速信号线与模拟信号线共线。
- 在电源线和地线周围设置环路,以降低电磁干扰对电路板的影响。
- 在可能受干扰的关键信号线上施加适当的滤波措施,如RC低通滤波器。
```mermaid
graph TD;
A[MCU] -->|信号线| B(双绞线)
B -->|连接| C(屏蔽线缆)
C -->|传输至| D[主机]
E[电源线] -->|环路设计| F[电路板设计]
F -->|滤波措施| G[RC低通滤波器]
```
通过上述措施的设计与实施,可以确保HID键盘在各种环境下都有稳定和可靠的性能表现。
# 5. HID键盘的软件开发实践
## 5.1 键盘映射与自定义
### 5.1.1 键盘映射原理
HID键盘的核心功能之一就是能够将按键映射到计算机系统中的特定功能或字符。键盘映射(Key Mapping)是通过软件配置来改变键盘的输入行为的过程。在软件层面上,这一过程涉及到将物理按键的状态转换成操作系统能够理解的输入事件。映射过程可以简单到改变一个按键的输出字符,也可以复杂到触发一系列操作命令或执行宏指令。
键盘映射的原理可以从硬件层和软件层两个维度来分析。从硬件层面来说,HID设备发送的是报告描述符(Report Descriptor),这是一种用来说明设备如何与主机通信的数据结构。操作系统通过读取报告描述符来了解设备的按键布局、功能及对应的编码方式。而从软件层面来说,键盘映射通常是在驱动程序或者操作系统层面实现的,通过修改按键与系统功能的对应关系来完成自定义映射。
为了实现键盘映射,开发者需要具备对HID报告描述符的深入理解,以及对目标操作系统的输入事件处理机制的了解。例如,在Windows操作系统中,可以利用Windows HID类驱动程序接口(HIDClass.sys)来实现自定义映射。而对于Linux系统,则可以通过修改HID设备的input子系统来改变按键的行为。
### 5.1.2 自定义按键功能实现
自定义按键功能的实现对于高级用户和游戏用户来说尤为关键,它可以提升用户的操作效率和体验。实现自定义按键功能,通常需要开发者进行以下步骤:
1. 读取HID设备的报告描述符以理解其按键布局和功能。
2. 根据报告描述符,使用编程语言编写映射逻辑代码。
3. 将自定义逻辑集成到HID驱动或应用程序中。
4. 在应用程序中实现用户界面,让用户可以方便地修改和保存按键映射配置。
下面是一个简单的示例代码,展示了如何在Linux环境下使用C语言读取HID设备的报告描述符:
```c
#include <stdio.h>
#include <stdlib.h>
#include <linux/hid.h>
#include <libusb-1.0/libusb.h>
// 初始化libusb库
libusb_init(NULL);
// 查找指定的HID设备
struct libusb_device_handle *dev_handle = NULL;
dev_handle = libusb_open_device_with_vid_pid(NULL, <VendorID>, <ProductID>);
// 获取报告描述符
unsigned char *report_descriptor;
int descriptor_length;
int result = libusb_get_descriptor(dev_handle, LIBUSB_DT_REPORT, 0, report_descriptor, &descriptor_length);
// 打印报告描述符
for (int i = 0; i < descriptor_length; i++) {
printf("%02X ", report_descriptor[i]);
if ((i + 1) % 16 == 0) {
printf("\n");
}
}
libusb_close(dev_handle);
libusb_exit(NULL);
```
在上述代码中,替换 `<VendorID>` 和 `<ProductID>` 为具体设备的值,以获得正确的报告描述符。该代码段仅读取并打印了报告描述符的内容,实际应用中还需要进一步分析报告描述符,找出特定按键对应的字段,并编写逻辑来修改这些字段的值。
接下来,将这些逻辑整合到驱动程序或应用程序中,提供用户配置接口。对于复杂的自定义功能,如宏编程,还需要进一步开发能够在特定条件下触发预设动作的逻辑。
## 5.2 驱动程序与操作系统交互
### 5.2.1 操作系统中的HID驱动架构
操作系统的HID驱动架构提供了与HID设备交互的底层机制。在大多数现代操作系统中,HID驱动主要负责处理设备报告、实现设备的枚举和初始化、以及与设备进行数据通信。HID驱动架构的设计目标是抽象化硬件,为上层应用提供统一的编程接口和行为。
在Windows操作系统中,HID类驱动程序(HIDCLASS.sys)是内核模式下处理HID设备的驱动程序。它提供了一套标准的API,允许用户模式下的应用程序读取HID设备的输入报告和发送输出报告。同时,它还负责解析报告描述符,并将按键事件映射成系统事件,如键盘按键事件、鼠标移动事件等。
在Linux操作系统中,HID子系统采用一种模块化的设计,提供了 HID RAW 事件和 HID通用输入事件两种接口。HID RAW事件是原始的输入报告数据,可以直接由应用程序读取,而无需通过任何解释。HID通用输入事件是内核已经解析过的事件,可以直接由输入子系统分发到应用程序。
### 5.2.2 驱动程序开发与调试技巧
HID键盘的驱动程序开发是一项复杂的任务,涉及到硬件与软件的深入交互。下面是一些核心的开发与调试技巧:
1. **正确读取报告描述符** - 驱动程序开发的首要步骤是正确读取和解析HID设备的报告描述符。报告描述符中包含了按键布局、数值范围和其他重要的设备信息。开发者需要对HID报告描述符的格式有充分的了解。
2. **实现设备初始化** - 设备的初始化流程涉及硬件枚举、报告描述符的获取和设备配置。在此阶段,需要确保操作系统正确识别了设备,并且能够处理设备的输入输出。
3. **事件处理与分发** - 驱动程序需要能够处理来自HID设备的输入事件,并且将它们转换为操作系统可理解的事件。这涉及到对输入子系统API的调用。
4. **自定义功能实现** - 要实现自定义按键和宏命令,开发者需要在驱动程序中添加额外的逻辑。这通常包括添加新的数据输入输出路径和处理逻辑。
5. **调试与验证** - 在驱动开发过程中,调试是不可避免的步骤。推荐使用操作系统提供的调试工具,如Windows的Driver Verifier和Linux的kgdb,来辅助验证驱动程序的行为。
6. **测试与兼容性** - 驱动程序开发后需要在不同的系统环境和硬件配置上进行测试,以确保其稳定性和兼容性。
通过上述步骤和技巧,开发者能够为HID键盘构建出高效、稳定的驱动程序,从而提升用户的使用体验。
# 6. HID键盘的高级应用与创新思路
HID键盘虽然最初被设计为输入设备,但随着技术的发展,它们已经拓展出了更多高级应用和创新思路。本章将详细探讨多媒体控制、宏编程,以及跨平台兼容性和无线技术在HID键盘中的应用。
## 6.1 多媒体控制与宏编程
随着用户需求的增加,HID键盘不仅仅局限于文本输入,多媒体控制和宏编程成为了现代键盘的必备功能。
### 6.1.1 多媒体键的实现
多媒体键允许用户快速访问控制音量、播放音乐、视频播放控制等功能,无需切换到相应的应用程序。以下是实现多媒体键的基本步骤:
1. **定义按键功能** - 每个多媒体键对应一个特定的HID Usage ID,必须在键盘的HID报告描述符中定义这些ID。
2. **编写固件逻辑** - 在键盘的固件中添加代码,当按下对应的按键时,通过HID报告发送相应的Usage ID。
3. **操作系统支持** - 确保操作系统能够识别并执行这些Usage ID对应的命令。这通常涉及系统级的API调用或使用操作系统提供的功能。
示例代码片段(伪代码):
```c
#define VOLUME_UP_USAGE 0x100647
#define VOLUME_DOWN_USAGE 0x100648
#define PLAY_PAUSE_USAGE 0x10066F
void handle_media_keys() {
if (is_key_pressed(VOLUME_UP_USAGE)) {
// 增加系统音量
} else if (is_key_pressed(VOLUME_DOWN_USAGE)) {
// 减少系统音量
} else if (is_key_pressed(PLAY_PAUSE_USAGE)) {
// 播放/暂停媒体
}
}
```
### 6.1.2 宏编程的原理与应用
宏编程允许用户记录一系列按键操作,并将这些操作绑定到一个单独的按键上,从而实现复杂操作的快速执行。以下是宏编程实现的基本步骤:
1. **记录按键序列** - 用户通过特定方式激活宏录制功能,之后正常操作键盘和鼠标,软件记录下这些操作。
2. **存储和触发** - 将记录的按键序列存储在键盘的存储空间中,并为该序列分配一个触发键。
3. **执行宏操作** - 当触发键被按下时,键盘执行存储的按键序列,模拟用户的操作。
宏编程在游戏和专业领域非常受欢迎,因为它可以大幅提升效率和操作体验。
## 6.2 跨平台兼容性与无线技术
随着用户使用的设备多样化,HID键盘的跨平台兼容性和无线技术成为了提高用户满意度的关键。
### 6.2.1 跨平台兼容性挑战与解决方案
跨平台兼容性指的是HID键盘能够在不同的操作系统和设备上无差异地工作。以下是解决兼容性问题的方法:
1. **遵循标准规范** - 设计时严格遵守HID协议的官方规范,确保设备能够被大多数系统识别。
2. **操作系统特定支持** - 为特定操作系统编写专门的驱动程序或使用操作系统提供的内置支持。
3. **用户自定义** - 提供用户可配置的设置选项,以适应不同平台的特定要求。
### 6.2.2 无线HID键盘技术与标准
无线HID键盘技术包括蓝牙、2.4GHz无线等,每种技术都有其独特的优势和标准。以下是无线技术的主要考虑因素:
1. **连接稳定性** - 设备应维持稳定的连接,避免延迟或断线。
2. **电池寿命** - 设备的电池寿命需要满足用户期望,一些键盘设计包括低功耗待机模式。
3. **加密与安全** - 对传输的数据进行加密,确保用户信息的安全。
表 6.1:无线HID键盘技术对比
| 特性 | 蓝牙 | 2.4GHz |
|------------|-------------------|-------------------|
| 连接范围 | 短距离至中等距离 | 短距离至中等距离 |
| 连接方式 | 配对连接 | 动态频率跳变 |
| 带宽 | 低 | 高 |
| 电池寿命 | 较短 | 较长 |
| 兼容性 | 多平台兼容 | 多平台兼容 |
| 安全性 | 加密传输 | 加密传输 |
随着技术的进步,HID键盘的应用和创新将不断拓宽,使它们不仅仅作为输入设备,而是能够与用户的工作流紧密集成,实现更高效的互动体验。
0
0