samples: usb: add HID keyboard sample
Add HID keyboard sample for the new experimental USB device support. This is a limited and not fully compliant HID keyboard implementation. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
parent
fce6b20f61
commit
859cc2f1fe
10 changed files with 408 additions and 0 deletions
9
samples/subsys/usb/hid-keyboard/CMakeLists.txt
Normal file
9
samples/subsys/usb/hid-keyboard/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(hid-keyboard)
|
||||
|
||||
include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
9
samples/subsys/usb/hid-keyboard/Kconfig
Normal file
9
samples/subsys/usb/hid-keyboard/Kconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Source common USB sample options used to initialize new experimental USB
|
||||
# device stack. The scope of these options is limited to USB samples in project
|
||||
# tree, you cannot use them in your own application.
|
||||
source "samples/subsys/usb/common/Kconfig.sample_usbd"
|
||||
|
||||
source "Kconfig.zephyr"
|
36
samples/subsys/usb/hid-keyboard/README.rst
Normal file
36
samples/subsys/usb/hid-keyboard/README.rst
Normal file
|
@ -0,0 +1,36 @@
|
|||
.. zephyr:code-sample:: usb-hid-keyboard
|
||||
:name: USB HID keyboard
|
||||
:relevant-api: usbd_api usbd_hid_class input_interface
|
||||
|
||||
Implement a basic HID keyboard device.
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample application demonstrates the HID keyboard implementation using the
|
||||
new experimental USB device stack.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
This project requires an experimental USB device driver (UDC API) and uses the
|
||||
:ref:`input` API. There must be a :dtcompatible:`gpio-keys` group of buttons
|
||||
or keys defined at the board level that can generate input events.
|
||||
At least one key is required and up to four can be used. The first three keys
|
||||
are used for Num Lock, Caps Lock and Scroll Lock. The fourth key is used to
|
||||
report HID keys 1, 2, 3 and the right Alt modifier at once.
|
||||
|
||||
The example can use up to three LEDs, configured via the devicetree alias such
|
||||
as ``led0``, to indicate the state of the keyboard LEDs.
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
|
||||
This sample can be built for multiple boards, in this example we will build it
|
||||
for the nRF52840DK board:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/subsys/usb/hid-keyboard
|
||||
:board: nrf52840dk/nrf52840
|
||||
:goals: build flash
|
||||
:compact:
|
15
samples/subsys/usb/hid-keyboard/app.overlay
Normal file
15
samples/subsys/usb/hid-keyboard/app.overlay
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/ {
|
||||
hid_dev_0: hid_dev_0 {
|
||||
compatible = "zephyr,hid-device";
|
||||
interface-name = "HID0";
|
||||
protocol-code = "keyboard";
|
||||
in-report-size = <64>;
|
||||
in-polling-rate = <1000>;
|
||||
};
|
||||
};
|
14
samples/subsys/usb/hid-keyboard/large_in_report.overlay
Normal file
14
samples/subsys/usb/hid-keyboard/large_in_report.overlay
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/ {
|
||||
hid_dev_0: hid_dev_0 {
|
||||
compatible = "zephyr,hid-device";
|
||||
interface-name = "HID0";
|
||||
in-report-size = <256>;
|
||||
in-polling-rate = <1000>;
|
||||
};
|
||||
};
|
15
samples/subsys/usb/hid-keyboard/large_out_report.overlay
Normal file
15
samples/subsys/usb/hid-keyboard/large_out_report.overlay
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "large_in_report.overlay"
|
||||
|
||||
/ {
|
||||
hid_dev_0: hid_dev_0 {
|
||||
compatible = "zephyr,hid-device";
|
||||
out-report-size = <128>;
|
||||
out-polling-rate = <16000>;
|
||||
};
|
||||
};
|
15
samples/subsys/usb/hid-keyboard/out_report.overlay
Normal file
15
samples/subsys/usb/hid-keyboard/out_report.overlay
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "app.overlay"
|
||||
|
||||
/ {
|
||||
hid_dev_0: hid_dev_0 {
|
||||
compatible = "zephyr,hid-device";
|
||||
out-report-size = <64>;
|
||||
out-polling-rate = <16000>;
|
||||
};
|
||||
};
|
12
samples/subsys/usb/hid-keyboard/prj.conf
Normal file
12
samples/subsys/usb/hid-keyboard/prj.conf
Normal file
|
@ -0,0 +1,12 @@
|
|||
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||
CONFIG_USBD_HID_SUPPORT=y
|
||||
|
||||
CONFIG_LOG=y
|
||||
CONFIG_USBD_LOG_LEVEL_WRN=y
|
||||
CONFIG_USBD_HID_LOG_LEVEL_WRN=y
|
||||
CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y
|
||||
CONFIG_SAMPLE_USBD_PID=0x0007
|
||||
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_INPUT=y
|
||||
CONFIG_INPUT_MODE_SYNCHRONOUS=y
|
26
samples/subsys/usb/hid-keyboard/sample.yaml
Normal file
26
samples/subsys/usb/hid-keyboard/sample.yaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
sample:
|
||||
name: USB HID keyboard sample
|
||||
common:
|
||||
harness: button
|
||||
filter: dt_alias_exists("sw0") and dt_alias_exists("led0")
|
||||
depends_on:
|
||||
- usb_device
|
||||
- gpio
|
||||
platform_allow:
|
||||
- nrf52840dk/nrf52840
|
||||
- frdm_k64f
|
||||
tests:
|
||||
sample.usbd.hid-keyboard:
|
||||
tags: usb
|
||||
sample.usbd.hid-keyboard.out-report:
|
||||
tags: usb
|
||||
extra_args:
|
||||
- EXTRA_DTC_OVERLAY_FILE="out_report.overlay"
|
||||
sample.usbd.hid-keyboard.large-report:
|
||||
tags: usb
|
||||
extra_args:
|
||||
- EXTRA_DTC_OVERLAY_FILE="large_in_report.overlay"
|
||||
sample.usbd.hid-keyboard.large-out-report:
|
||||
tags: usb
|
||||
extra_args:
|
||||
- EXTRA_DTC_OVERLAY_FILE="large_out_report.overlay"
|
257
samples/subsys/usb/hid-keyboard/src/main.c
Normal file
257
samples/subsys/usb/hid-keyboard/src/main.c
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sample_usbd.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/input/input.h>
|
||||
|
||||
#include <zephyr/usb/usbd.h>
|
||||
#include <zephyr/usb/class/usbd_hid.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||
|
||||
static const uint8_t hid_report_desc[] = HID_KEYBOARD_REPORT_DESC();
|
||||
|
||||
enum kb_leds_idx {
|
||||
KB_LED_NUMLOCK = 0,
|
||||
KB_LED_CAPSLOCK,
|
||||
KB_LED_SCROLLLOCK,
|
||||
KB_LED_COUNT,
|
||||
};
|
||||
|
||||
static const struct gpio_dt_spec kb_leds[KB_LED_COUNT] = {
|
||||
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0}),
|
||||
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led1), gpios, {0}),
|
||||
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led2), gpios, {0}),
|
||||
};
|
||||
|
||||
enum kb_report_idx {
|
||||
KB_MOD_KEY = 0,
|
||||
KB_RESERVED,
|
||||
KB_KEY_CODE1,
|
||||
KB_KEY_CODE2,
|
||||
KB_KEY_CODE3,
|
||||
KB_KEY_CODE4,
|
||||
KB_KEY_CODE5,
|
||||
KB_KEY_CODE6,
|
||||
KB_REPORT_COUNT,
|
||||
};
|
||||
|
||||
struct kb_event {
|
||||
uint16_t code;
|
||||
int32_t value;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(kb_msgq, sizeof(struct kb_event), 2, 1);
|
||||
|
||||
static uint8_t __aligned(sizeof(void *)) report[KB_REPORT_COUNT];
|
||||
static uint32_t kb_duration;
|
||||
static bool kb_ready;
|
||||
|
||||
static void input_cb(struct input_event *evt)
|
||||
{
|
||||
struct kb_event kb_evt;
|
||||
|
||||
kb_evt.code = evt->code;
|
||||
kb_evt.value = evt->value;
|
||||
if (k_msgq_put(&kb_msgq, &kb_evt, K_NO_WAIT) != 0) {
|
||||
LOG_ERR("Failed to put new input event");
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_CALLBACK_DEFINE(NULL, input_cb);
|
||||
|
||||
static void kb_iface_ready(const struct device *dev, const bool ready)
|
||||
{
|
||||
LOG_INF("HID device %s interface is %s",
|
||||
dev->name, ready ? "ready" : "not ready");
|
||||
kb_ready = ready;
|
||||
}
|
||||
|
||||
static int kb_get_report(const struct device *dev,
|
||||
const uint8_t type, const uint8_t id, const uint16_t len,
|
||||
uint8_t *const buf)
|
||||
{
|
||||
LOG_WRN("Get Report not implemented, Type %u ID %u", type, id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kb_set_report(const struct device *dev,
|
||||
const uint8_t type, const uint8_t id, const uint16_t len,
|
||||
const uint8_t *const buf)
|
||||
{
|
||||
if (type != HID_REPORT_TYPE_OUTPUT) {
|
||||
LOG_WRN("Unsupported report type");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(kb_leds); i++) {
|
||||
if (kb_leds[i].port == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)gpio_pin_set_dt(&kb_leds[i], buf[0] & BIT(i));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Idle duration is stored but not used to calculate idle reports. */
|
||||
static void kb_set_idle(const struct device *dev,
|
||||
const uint8_t id, const uint32_t duration)
|
||||
{
|
||||
LOG_INF("Set Idle %u to %u", id, duration);
|
||||
kb_duration = duration;
|
||||
}
|
||||
|
||||
static uint32_t kb_get_idle(const struct device *dev, const uint8_t id)
|
||||
{
|
||||
LOG_INF("Get Idle %u to %u", id, kb_duration);
|
||||
return kb_duration;
|
||||
}
|
||||
|
||||
static void kb_set_protocol(const struct device *dev, const uint8_t proto)
|
||||
{
|
||||
LOG_INF("Protocol changed to %s",
|
||||
proto == 0U ? "Boot Protocol" : "Report Protocol");
|
||||
}
|
||||
|
||||
static void kb_output_report(const struct device *dev, const uint16_t len,
|
||||
const uint8_t *const buf)
|
||||
{
|
||||
LOG_HEXDUMP_DBG(buf, len, "o.r.");
|
||||
kb_set_report(dev, HID_REPORT_TYPE_OUTPUT, 0U, len, buf);
|
||||
}
|
||||
|
||||
struct hid_device_ops kb_ops = {
|
||||
.iface_ready = kb_iface_ready,
|
||||
.get_report = kb_get_report,
|
||||
.set_report = kb_set_report,
|
||||
.set_idle = kb_set_idle,
|
||||
.get_idle = kb_get_idle,
|
||||
.set_protocol = kb_set_protocol,
|
||||
.output_report = kb_output_report,
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct usbd_contex *sample_usbd;
|
||||
const struct device *hid_dev;
|
||||
int ret;
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(kb_leds); i++) {
|
||||
if (kb_leds[i].port == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!gpio_is_ready_dt(&kb_leds[i])) {
|
||||
LOG_ERR("LED device %s is not ready", kb_leds[i].port->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&kb_leds[i], GPIO_OUTPUT_INACTIVE);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to configure the LED pin, %d", ret);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
hid_dev = DEVICE_DT_GET_ONE(zephyr_hid_device);
|
||||
if (!device_is_ready(hid_dev)) {
|
||||
LOG_ERR("HID Device is not ready");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = hid_device_register(hid_dev,
|
||||
hid_report_desc, sizeof(hid_report_desc),
|
||||
&kb_ops);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to register HID Device, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sample_usbd = sample_usbd_init_device(NULL);
|
||||
if (sample_usbd == NULL) {
|
||||
LOG_ERR("Failed to initialize USB device");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = usbd_enable(sample_usbd);
|
||||
if (ret) {
|
||||
LOG_ERR("Failed to enable device support");
|
||||
return ret;
|
||||
}
|
||||
|
||||
LOG_INF("HID keyboard sample is initialized");
|
||||
|
||||
while (true) {
|
||||
struct kb_event kb_evt;
|
||||
|
||||
k_msgq_get(&kb_msgq, &kb_evt, K_FOREVER);
|
||||
|
||||
switch (kb_evt.code) {
|
||||
case INPUT_KEY_0:
|
||||
if (kb_evt.value) {
|
||||
report[KB_KEY_CODE1] = HID_KEY_NUMLOCK;
|
||||
} else {
|
||||
report[KB_KEY_CODE1] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case INPUT_KEY_1:
|
||||
if (kb_evt.value) {
|
||||
report[KB_KEY_CODE2] = HID_KEY_CAPSLOCK;
|
||||
} else {
|
||||
report[KB_KEY_CODE2] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case INPUT_KEY_2:
|
||||
if (kb_evt.value) {
|
||||
report[KB_KEY_CODE3] = HID_KEY_SCROLLLOCK;
|
||||
} else {
|
||||
report[KB_KEY_CODE3] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case INPUT_KEY_3:
|
||||
if (kb_evt.value) {
|
||||
report[KB_MOD_KEY] = HID_KBD_MODIFIER_RIGHT_ALT;
|
||||
report[KB_KEY_CODE4] = HID_KEY_1;
|
||||
report[KB_KEY_CODE5] = HID_KEY_2;
|
||||
report[KB_KEY_CODE6] = HID_KEY_3;
|
||||
} else {
|
||||
report[KB_MOD_KEY] = HID_KBD_MODIFIER_NONE;
|
||||
report[KB_KEY_CODE4] = 0;
|
||||
report[KB_KEY_CODE5] = 0;
|
||||
report[KB_KEY_CODE6] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOG_INF("Unrecognized input code %u value %d",
|
||||
kb_evt.code, kb_evt.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!kb_ready) {
|
||||
LOG_INF("USB HID device is not ready");
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = hid_device_submit_report(hid_dev, sizeof(report), report);
|
||||
if (ret) {
|
||||
LOG_ERR("HID submit report error, %d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue