Skip to content

Commit

Permalink
samples: usb: add HID keyboard sample
Browse files Browse the repository at this point in the history
Add HID keyboard sample for the new experimental USB device support.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
  • Loading branch information
jfischer-no committed Dec 20, 2023
1 parent cb2b88c commit cfeb50b
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 0 deletions.
9 changes: 9 additions & 0 deletions samples/subsys/usb/hid-keyboard/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions samples/subsys/usb/hid-keyboard/Kconfig
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 11 additions & 0 deletions samples/subsys/usb/hid-keyboard/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. 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.
16 changes: 16 additions & 0 deletions samples/subsys/usb/hid-keyboard/app.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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";
boot-protocol-code = "keyboard";
polling-rate = <1>;
in-report-size = <64>;
out-report-size = <64>;
};
};
11 changes: 11 additions & 0 deletions samples/subsys/usb/hid-keyboard/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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_DBG=y
CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y
CONFIG_SAMPLE_USBD_PID=0x0007

CONFIG_GPIO=y
CONFIG_INPUT=y
10 changes: 10 additions & 0 deletions samples/subsys/usb/hid-keyboard/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sample:
name: USB HID keyboard sample
tests:
sample.usb.hid-keyboard:
depends_on:
- usb_device
- gpio
harness: button
filter: dt_alias_exists("sw0") and dt_alias_exists("led0")
tags: usb
254 changes: 254 additions & 0 deletions samples/subsys/usb/hid-keyboard/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <sample_usbd.h>

#include <string.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/input/input.h>
#include <zephyr/sys/util.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,
};

static uint8_t report[KB_REPORT_COUNT];
static K_SEM_DEFINE(report_sem, 0, 1);
static uint32_t kb_duration;
static bool prankme;

static void input_cb(struct input_event *evt)
{
uint8_t tmp[KB_REPORT_COUNT];

(void)memcpy(tmp, report, sizeof(tmp));

switch (evt->code) {
case INPUT_KEY_0:
tmp[KB_KEY_CODE1] = 0;
if (evt->value) {
tmp[KB_KEY_CODE1] = HID_KEY_NUMLOCK;
}

break;
case INPUT_KEY_1:
tmp[KB_KEY_CODE1] = 0;
if (evt->value) {
tmp[KB_KEY_CODE1] = HID_KEY_CAPSLOCK;
}

break;
case INPUT_KEY_2:
tmp[KB_KEY_CODE1] = 0;
if (evt->value) {
tmp[KB_KEY_CODE1] = HID_KEY_SCROLLLOCK;
}

break;
case INPUT_KEY_3:
tmp[KB_KEY_CODE1] = 0;
if (evt->value) {
tmp[KB_KEY_CODE1] = HID_KEY_ENTER;
prankme = !prankme;
}

break;
default:
LOG_INF("Unrecognized input code %u value %d",
evt->code, evt->value);
return;
}

if (memcmp(tmp, report, sizeof(tmp))) {
memcpy(report, tmp, sizeof(report));
k_sem_give(&report_sem);
}
}

INPUT_CALLBACK_DEFINE(NULL, input_cb);

static void do_prankme(void)
{
static uint32_t cnt;

switch (cnt & 3) {
case 0:
report[KB_KEY_CODE1] = HID_KEY_NUMLOCK;
break;
case 1:
report[KB_KEY_CODE1] = HID_KEY_CAPSLOCK;
break;
case 2:
report[KB_KEY_CODE1] = HID_KEY_SCROLLLOCK;
break;
case 3:
report[KB_KEY_CODE1] = 0;
break;
}

cnt ++;

Check failure on line 124 in samples/subsys/usb/hid-keyboard/src/main.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

SPACING

samples/subsys/usb/hid-keyboard/src/main.c:124 space prohibited before that '++' (ctx:WxO)
}

static void kb_iface_ready(const struct device *dev, const bool ready)
{
LOG_INF("HID device %p interface is %s", dev, ready ? "ready" : "not 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)
{
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)
{
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;
}

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, 2U, 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();
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;
}

while (true) {
if (prankme) {
k_msleep(10);
do_prankme();
} else {
k_sem_take(&report_sem, K_FOREVER);
}

ret = hid_device_submit_report(hid_dev, sizeof(report), report, true);
if (ret) {
LOG_ERR("HID write error, %d", ret);
}
}

return 0;
}

0 comments on commit cfeb50b

Please sign in to comment.