initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/build/**/*
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
set(PROJECT ble_hid)
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk)
|
||||||
|
set(PICO_PIO_USB_PATH /home/kenji/programming/pico/c/Pico-PIO-USB)
|
||||||
|
set(PICO_BOARD pico_w)
|
||||||
|
include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
||||||
|
project(${PROJECT} C CXX ASM)
|
||||||
|
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
add_subdirectory(${PICO_PIO_USB_PATH} pico_pio_usb)
|
||||||
|
|
||||||
|
add_executable(${PROJECT})
|
||||||
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
bt_device.c
|
||||||
|
hid_report.c
|
||||||
|
main.c
|
||||||
|
usb_descriptors.c
|
||||||
|
usb_device.c
|
||||||
|
usb_host.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# print memory usage, enable all warnings
|
||||||
|
target_link_options(${PROJECT} PRIVATE -Xlinker --print-memory-usage)
|
||||||
|
target_compile_options(${PROJECT} PRIVATE ) #-Wall -Wextra
|
||||||
|
|
||||||
|
# use tinyusb pio
|
||||||
|
target_compile_definitions(${PROJECT} PRIVATE PIO_USB_USE_TINYUSB)
|
||||||
|
|
||||||
|
# needed so tinyusb can find tusb_config.h
|
||||||
|
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT} PRIVATE
|
||||||
|
pico_btstack_ble
|
||||||
|
pico_btstack_cyw43
|
||||||
|
pico_cyw43_arch_none
|
||||||
|
pico_multicore
|
||||||
|
pico_pio_usb
|
||||||
|
pico_stdlib
|
||||||
|
tinyusb_board
|
||||||
|
tinyusb_device
|
||||||
|
tinyusb_host
|
||||||
|
tinyusb_pico_pio_usb
|
||||||
|
)
|
||||||
|
|
||||||
|
pico_btstack_make_gatt_header(ble_hid PRIVATE "${CMAKE_CURRENT_LIST_DIR}/ble_hid.gatt")
|
||||||
|
|
||||||
|
pico_add_extra_outputs(${PROJECT})
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Pico BLE HID - USB HID to BLE adapter using Raspberry Pi Pico W
|
||||||
|
Copyright (C) 2025 Kenji Kozai
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the License,
|
||||||
|
or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Pico BLE HID
|
||||||
|
|
||||||
|
This project uses a Raspberry Pi Pico W as a Bluetooth Low Energy adapter for
|
||||||
|
a wired USB HID input peripheral, allowing it to connect to a host device
|
||||||
|
over BLE using HID over GATT Profile.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
|
||||||
|
You will need the following hardware to make the device:
|
||||||
|
- Raspberry Pi Pico W
|
||||||
|
- USB extension cable
|
||||||
|
|
||||||
|
You will need to cut the USB extension in half and connect the wires from the
|
||||||
|
female end to the Raspberry Pi Pico W. The default configuration is to attach
|
||||||
|
the USB's green wire to pin 1/GP0 and USB's white wire to pin 2/GP1. You will
|
||||||
|
also need to connect the red to 5V VBUS/VSYS (since you'll be powering from the
|
||||||
|
USB connection, VBUS should be fine) and the black to any ground pin. Pin 38
|
||||||
|
is the closest and most convenient, or the ground test point TP1 on the back
|
||||||
|
will also work. While you can connect the Raspberry Pi Pico to the host device
|
||||||
|
using the micro USB port and a micro USB cable, since you have already
|
||||||
|
sacrificed half of the USB extension cable, you might as well use the male half
|
||||||
|
to create a standard USB-A connection. If you wish to do so, then simply
|
||||||
|
connect the red and black wires of the male connector end to VBUS (5V) and GND,
|
||||||
|
respectively. For the data wires, you can solder them to the two test points
|
||||||
|
TP2 and TP3 on the back of the Raspberry Pi Pico. The white cable goes to TP2
|
||||||
|
and the green cable to TP3.
|
||||||
|
|
||||||
|
The individual wires on the USB can be fragile, so hot glue or other strain
|
||||||
|
relief is a good idea.
|
||||||
|
|
||||||
|
### Software
|
||||||
|
|
||||||
|
Download the ble_hid.uf2 file from the latest
|
||||||
|
[release](https://git.kkozai.com/kenji/pico_ble_hid/releases) and flash
|
||||||
|
onto the Raspberry Pi Pico W by holding down the BOOTSEL button while plugging
|
||||||
|
into your computer so that it appears as a USB drive, then transfer the
|
||||||
|
firmware file onto the Pico W. After unmounting, the firmware install is
|
||||||
|
complete.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Connect the Raspberry Pi Pico W into any USB power source (a good, small power
|
||||||
|
bank will work), then plug in the desired peripheral into the female USB
|
||||||
|
socket. After about a second, the onboard LED will light up, indicating it is
|
||||||
|
ready to be paired over Bluetooth LE.
|
||||||
|
|
||||||
|
From the host machine, go to your Bluetooth settings and pair with the
|
||||||
|
"Pico BLE HID" device. The LED should turn off when pairing begins, and it will
|
||||||
|
turn back on when pairing is complete and the Raspberry Pi Pico W is
|
||||||
|
ready to receive inputs. This will take a few seconds, so be patient.
|
||||||
|
Once the light is back on, your device should work over the Bluetooth
|
||||||
|
connection. When connecting a power-hungry device such as a gaming keyboard
|
||||||
|
with many LEDs, the sudden power draw when plugging in can cause issues, so it
|
||||||
|
is recommended to turn off the LEDs before connecting to the Raspberry Pi Pico
|
||||||
|
W.
|
||||||
|
|
||||||
|
In principle, the Pico BLE HID will work with peripherals plugged into
|
||||||
|
a USB hub, but compatibility will vary with the specific hardware used,
|
||||||
|
including the peripherals, USB hub, and operating system. This is because the
|
||||||
|
HID report descriptors for all interfaces on all devices are combined into a
|
||||||
|
single descriptor, which has a limitation of 512 bytes. Additionally, some OSes
|
||||||
|
may get confused with combinations of report descriptors that seemingly
|
||||||
|
conflict. This is particularly true with modern gaming peripherals which will
|
||||||
|
present as multiple interfaces of different types (e.g. a keyboard may present
|
||||||
|
as a combined keyboard, mouse, joystick, and consumer control, even if it does
|
||||||
|
not use any of the features of the other peripheral types), which can conflict
|
||||||
|
when another peripheral of one of those other types is plugged in. This also
|
||||||
|
uses up precious bytes of the limited space available for HID descriptors
|
||||||
|
over GATT. Powering multiple USB devices can also cause problems -- a powered
|
||||||
|
USB hub *may* help, but it is largely untested.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
This software is distributed under the
|
||||||
|
[GNU Lesser General Public License Version 3](LICENSE), with the exception of
|
||||||
|
the libraries in the following section.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
The project uses code from the following sources:
|
||||||
|
- [BTstack](https://github.com/bluekitchen/btstack/) for code from
|
||||||
|
[hog_keyboard_demo.c](https://github.com/bluekitchen/btstack/blob/master/example/hog_keyboard_demo.c)
|
||||||
|
and
|
||||||
|
[hog_keyboard_demo.gatt](https://github.com/bluekitchen/btstack/blob/master/example/hog_keyboard_demo.gatt) distributed under their
|
||||||
|
[non-commercial license](https://github.com/bluekitchen/btstack/tree/master?tab=License-1-ov-file)
|
||||||
|
and the
|
||||||
|
[supplemental license](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_btstack/LICENSE.RP)
|
||||||
|
from Raspberry Pi
|
||||||
|
- [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB/) for templates
|
||||||
|
used from the [host_hid_to_device_cdc](https://github.com/sekigon-gonnoc/Pico-PIO-USB/tree/control-keyboard-led/examples/host_hid_to_device_cdc)
|
||||||
|
example released under the MIT license
|
||||||
|
- [Raspberry Pi Pico SDK Examples](https://github.com/raspberrypi/pico-examples)
|
||||||
|
for a modified version of
|
||||||
|
[pico_w/bt/standalone/btstack_config_common.h](btstack_config_common.h)
|
||||||
|
distributed under the BSD-3-Clause license
|
||||||
|
- [TinyUSB](https://github.com/hathach/tinyusb) for templates used in
|
||||||
|
[tusb_config.h](tusb_config.h) distributed under the MIT license
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
PRIMARY_SERVICE, GAP_SERVICE
|
||||||
|
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Pico BLE HID"
|
||||||
|
|
||||||
|
// add Battery Service
|
||||||
|
#import <battery_service.gatt>
|
||||||
|
|
||||||
|
// add Device ID Service
|
||||||
|
#import <device_information_service.gatt>
|
||||||
|
|
||||||
|
// Human Interface Device 1812
|
||||||
|
PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE, DYNAMIC | READ | WRITE_WITHOUT_RESPONSE,
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 1, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 1, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 2, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 2, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 3, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 3, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 4, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 4, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 5, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 5, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 6, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 6, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 7, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 7, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 8, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 8, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 9, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 9, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 10, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 10, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 11, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 11, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 12, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 12, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 13, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 13, 1
|
||||||
|
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 14, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 14, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 15, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 15, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
||||||
|
// fixed report id = 16, type = Input (1)
|
||||||
|
REPORT_REFERENCE, READ, 16, 1
|
||||||
|
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP, DYNAMIC | READ,
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT, DYNAMIC | READ | WRITE | NOTIFY,
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT, DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE,
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT, DYNAMIC | READ | WRITE | NOTIFY,
|
||||||
|
// bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION, READ, 01 01 00 02
|
||||||
|
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT, DYNAMIC | WRITE_WITHOUT_RESPONSE,
|
||||||
|
|
||||||
|
PRIMARY_SERVICE, GATT_SERVICE
|
||||||
|
CHARACTERISTIC, GATT_DATABASE_HASH, READ,
|
||||||
|
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
#define BTSTACK_FILE__ "bt_device.c"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include "pico/btstack_cyw43.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "btstack.h"
|
||||||
|
#include "ble/gatt-service/battery_service_server.h"
|
||||||
|
#include "ble/gatt-service/device_information_service_server.h"
|
||||||
|
#include "ble/gatt-service/hids_device.h"
|
||||||
|
|
||||||
|
#include "hid_report.h"
|
||||||
|
#include "usb_device.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
|
||||||
|
#include "bt_device.h"
|
||||||
|
#include "ble_hid.h"
|
||||||
|
|
||||||
|
static btstack_packet_callback_registration_t hci_event_callback_registration;
|
||||||
|
static btstack_packet_callback_registration_t l2cap_event_callback_registration;
|
||||||
|
static btstack_packet_callback_registration_t sm_event_callback_registration;
|
||||||
|
static uint8_t battery = 100;
|
||||||
|
hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID;
|
||||||
|
static uint8_t protocol_mode = 1;
|
||||||
|
|
||||||
|
static hids_device_report_t *dev_report_storage;
|
||||||
|
|
||||||
|
const uint8_t adv_data[] = {
|
||||||
|
// Flags general discoverable, BR/EDR not supported
|
||||||
|
0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
|
||||||
|
// Name
|
||||||
|
0x0d, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', ' ', 'B', 'L', 'E', ' ', 'H','I','D',
|
||||||
|
// 16-bit Service UUIDs
|
||||||
|
0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE & 0xff, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE >> 8,
|
||||||
|
// Appearance HID - Keyboard (Category 15, Sub-Category 1)
|
||||||
|
0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC1, 0x03,
|
||||||
|
};
|
||||||
|
const uint8_t adv_data_len = sizeof(adv_data);
|
||||||
|
|
||||||
|
static void bt_hid_setup(void);
|
||||||
|
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||||
|
|
||||||
|
//extern uint8_t desc_hid_report[];
|
||||||
|
//extern uint16_t desc_hid_report_len;
|
||||||
|
|
||||||
|
// start BTstack
|
||||||
|
void btstack_main(void){
|
||||||
|
bt_hid_setup();
|
||||||
|
|
||||||
|
init_report_buf();
|
||||||
|
|
||||||
|
hci_power_control(HCI_POWER_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_setup(void) {
|
||||||
|
cyw43_arch_init();
|
||||||
|
|
||||||
|
l2cap_init();
|
||||||
|
|
||||||
|
// setup SM: Display only
|
||||||
|
sm_init();
|
||||||
|
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
||||||
|
//sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);
|
||||||
|
sm_set_authentication_requirements(SM_AUTHREQ_BONDING);
|
||||||
|
|
||||||
|
// setup ATT server
|
||||||
|
att_server_init(profile_data, NULL, NULL);
|
||||||
|
|
||||||
|
// setup battery service
|
||||||
|
battery_service_server_init(battery);
|
||||||
|
|
||||||
|
// setup device information service
|
||||||
|
device_information_service_server_init();
|
||||||
|
|
||||||
|
// setup HID Device service
|
||||||
|
dev_report_storage = (hids_device_report_t *)malloc(sizeof(hids_device_report_t)*NUM_REPORT_IDS);
|
||||||
|
hids_device_init_with_storage(0, get_desc_hid_report(), get_desc_hid_report_len(), NUM_REPORT_IDS, dev_report_storage);
|
||||||
|
|
||||||
|
// setup advertisements
|
||||||
|
uint16_t adv_int_min = 0x0030;
|
||||||
|
uint16_t adv_int_max = 0x0030;
|
||||||
|
uint8_t adv_type = 0;
|
||||||
|
bd_addr_t null_addr;
|
||||||
|
memset(null_addr, 0, 6);
|
||||||
|
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
|
||||||
|
gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data);
|
||||||
|
gap_advertisements_enable(1);
|
||||||
|
|
||||||
|
// register for HCI events
|
||||||
|
hci_event_callback_registration.callback = &packet_handler;
|
||||||
|
hci_add_event_handler(&hci_event_callback_registration);
|
||||||
|
|
||||||
|
// register for connection parameter updates
|
||||||
|
l2cap_event_callback_registration.callback = &packet_handler;
|
||||||
|
l2cap_add_event_handler(&l2cap_event_callback_registration);
|
||||||
|
|
||||||
|
// register for SM events
|
||||||
|
sm_event_callback_registration.callback = &packet_handler;
|
||||||
|
sm_add_event_handler(&sm_event_callback_registration);
|
||||||
|
|
||||||
|
// register for HIDS
|
||||||
|
hids_device_register_packet_handler(packet_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler for BTStack packets
|
||||||
|
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||||
|
(void) channel;
|
||||||
|
(void) size;
|
||||||
|
|
||||||
|
uint16_t conn_interval;
|
||||||
|
|
||||||
|
if (packet_type != HCI_EVENT_PACKET) return;
|
||||||
|
|
||||||
|
switch (hci_event_packet_get_type(packet)) {
|
||||||
|
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||||||
|
con_handle = HCI_CON_HANDLE_INVALID;
|
||||||
|
printf("Disconnected\n");
|
||||||
|
|
||||||
|
set_host_state(HOST_STOP_LISTEN);
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
|
||||||
|
break;
|
||||||
|
case SM_EVENT_JUST_WORKS_REQUEST:
|
||||||
|
printf("Just Works requested\n");
|
||||||
|
sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
|
||||||
|
break;
|
||||||
|
case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
|
||||||
|
printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
|
||||||
|
sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
|
||||||
|
break;
|
||||||
|
case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
|
||||||
|
printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
|
||||||
|
break;
|
||||||
|
case L2CAP_EVENT_CONNECTION_PARAMETER_UPDATE_RESPONSE:
|
||||||
|
printf("L2CAP Connection Parameter Update Complete, response: %x\n", l2cap_event_connection_parameter_update_response_get_result(packet));
|
||||||
|
break;
|
||||||
|
case HCI_EVENT_LE_META:
|
||||||
|
switch (hci_event_le_meta_get_subevent_code(packet)) {
|
||||||
|
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
|
||||||
|
// print connection parameters (without using float operations)
|
||||||
|
conn_interval = hci_subevent_le_connection_complete_get_conn_interval(packet);
|
||||||
|
printf("LE Connection Complete:\n");
|
||||||
|
printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3));
|
||||||
|
printf("- Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet));
|
||||||
|
break;
|
||||||
|
case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE:
|
||||||
|
// print connection parameters (without using float operations)
|
||||||
|
conn_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet);
|
||||||
|
printf("LE Connection Update:\n");
|
||||||
|
printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3));
|
||||||
|
printf("- Connection Latency: %u\n", hci_subevent_le_connection_update_complete_get_conn_latency(packet));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HCI_EVENT_HIDS_META:
|
||||||
|
switch (hci_event_hids_meta_get_subevent_code(packet)){
|
||||||
|
case HIDS_SUBEVENT_INPUT_REPORT_ENABLE:
|
||||||
|
con_handle = hids_subevent_input_report_enable_get_con_handle(packet);
|
||||||
|
printf("Report Characteristic Subscribed %u\n", hids_subevent_input_report_enable_get_enable(packet));
|
||||||
|
|
||||||
|
set_host_state(HOST_START_LISTEN);
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
|
||||||
|
// request connection param update via L2CAP following Apple Bluetooth Design Guidelines
|
||||||
|
// gap_request_connection_parameter_update(con_handle, 12, 12, 4, 100); // 15 ms, 4, 1s
|
||||||
|
|
||||||
|
// directly update connection params via HCI following Apple Bluetooth Design Guidelines
|
||||||
|
// gap_update_connection_parameters(con_handle, 12, 12, 4, 100); // 60-75 ms, 4, 1s
|
||||||
|
|
||||||
|
break;
|
||||||
|
case HIDS_SUBEVENT_PROTOCOL_MODE:
|
||||||
|
protocol_mode = hids_subevent_protocol_mode_get_protocol_mode(packet);
|
||||||
|
printf("Protocol Mode: %s mode\n", hids_subevent_protocol_mode_get_protocol_mode(packet) ? "Report" : "Boot");
|
||||||
|
break;
|
||||||
|
case HIDS_SUBEVENT_CAN_SEND_NOW:
|
||||||
|
printf("HIDS_SUBEVENT_CAN_SEND_NOW\n");
|
||||||
|
send_report();
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1-cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to update the report map aka HID report descriptor on the BT interface
|
||||||
|
void update_desc_hid_report(void) {
|
||||||
|
hci_power_control(HCI_POWER_OFF);
|
||||||
|
|
||||||
|
uint16_t len = get_desc_hid_report_len();
|
||||||
|
if (len>0) {
|
||||||
|
hids_device_init_with_storage(0, get_desc_hid_report(), len, NUM_REPORT_IDS, dev_report_storage);
|
||||||
|
hci_power_control(HCI_POWER_ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef BT_DEVICE_H_
|
||||||
|
#define BT_DEVICE_H_
|
||||||
|
|
||||||
|
void btstack_main(void);
|
||||||
|
void update_desc_hid_report(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
|
||||||
|
#define _PICO_BTSTACK_BTSTACK_CONFIG_H
|
||||||
|
|
||||||
|
// BTstack features that can be enabled
|
||||||
|
#define ENABLE_LOG_INFO
|
||||||
|
#define ENABLE_LOG_ERROR
|
||||||
|
#define ENABLE_PRINTF_HEXDUMP
|
||||||
|
#define ENABLE_SCO_OVER_HCI
|
||||||
|
|
||||||
|
#ifdef ENABLE_BLE
|
||||||
|
#define ENABLE_GATT_CLIENT_PAIRING
|
||||||
|
#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE
|
||||||
|
#define ENABLE_LE_CENTRAL
|
||||||
|
#define ENABLE_LE_DATA_LENGTH_EXTENSION
|
||||||
|
#define ENABLE_LE_PERIPHERAL
|
||||||
|
#define ENABLE_LE_PRIVACY_ADDRESS_RESOLUTION
|
||||||
|
#define ENABLE_LE_SECURE_CONNECTIONS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_CLASSIC
|
||||||
|
#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE
|
||||||
|
#define ENABLE_GOEP_L2CAP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (ENABLE_CLASSIC) && defined(ENABLE_BLE)
|
||||||
|
#define ENABLE_CROSS_TRANSPORT_KEY_DERIVATION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// BTstack configuration. buffers, sizes, ...
|
||||||
|
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
|
||||||
|
#define HCI_ACL_PAYLOAD_SIZE (1691 + 4)
|
||||||
|
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
|
||||||
|
#define MAX_NR_AVDTP_CONNECTIONS 1
|
||||||
|
#define MAX_NR_AVDTP_STREAM_ENDPOINTS 1
|
||||||
|
#define MAX_NR_AVRCP_CONNECTIONS 2
|
||||||
|
#define MAX_NR_BNEP_CHANNELS 1
|
||||||
|
#define MAX_NR_BNEP_SERVICES 1
|
||||||
|
#define MAX_NR_BTSTACK_LINK_KEY_DB_MEMORY_ENTRIES 2
|
||||||
|
#define MAX_NR_GATT_CLIENTS 1
|
||||||
|
#define MAX_NR_HCI_CONNECTIONS 2
|
||||||
|
#define MAX_NR_HID_HOST_CONNECTIONS 1
|
||||||
|
#define MAX_NR_HIDS_CLIENTS 1
|
||||||
|
#define MAX_NR_HFP_CONNECTIONS 1
|
||||||
|
#define MAX_NR_L2CAP_CHANNELS 4
|
||||||
|
#define MAX_NR_L2CAP_SERVICES 3
|
||||||
|
#define MAX_NR_RFCOMM_CHANNELS 1
|
||||||
|
#define MAX_NR_RFCOMM_MULTIPLEXERS 1
|
||||||
|
#define MAX_NR_RFCOMM_SERVICES 1
|
||||||
|
#define MAX_NR_SERVICE_RECORD_ITEMS 4
|
||||||
|
#define MAX_NR_SM_LOOKUP_ENTRIES 3
|
||||||
|
#define MAX_NR_WHITELIST_ENTRIES 16
|
||||||
|
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16
|
||||||
|
|
||||||
|
// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
|
||||||
|
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
|
||||||
|
#define MAX_NR_CONTROLLER_SCO_PACKETS 3
|
||||||
|
|
||||||
|
// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
|
||||||
|
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
|
||||||
|
#define HCI_HOST_ACL_PACKET_LEN 1024
|
||||||
|
#define HCI_HOST_ACL_PACKET_NUM 3
|
||||||
|
#define HCI_HOST_SCO_PACKET_LEN 120
|
||||||
|
#define HCI_HOST_SCO_PACKET_NUM 3
|
||||||
|
|
||||||
|
// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
|
||||||
|
#define NVM_NUM_DEVICE_DB_ENTRIES 16
|
||||||
|
#define NVM_NUM_LINK_KEYS 16
|
||||||
|
|
||||||
|
// We don't give btstack a malloc, so use a fixed-size ATT DB.
|
||||||
|
#define MAX_ATT_DB_SIZE 512
|
||||||
|
|
||||||
|
// BTstack HAL configuration
|
||||||
|
#define HAVE_EMBEDDED_TIME_MS
|
||||||
|
|
||||||
|
// map btstack_assert onto Pico SDK assert()
|
||||||
|
#define HAVE_ASSERT
|
||||||
|
|
||||||
|
// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
|
||||||
|
#define HCI_RESET_RESEND_TIMEOUT_MS 1000
|
||||||
|
|
||||||
|
#define ENABLE_SOFTWARE_AES128
|
||||||
|
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS
|
||||||
|
|
||||||
|
#define HAVE_BTSTACK_STDIN
|
||||||
|
|
||||||
|
// To get the audio demos working even with HCI dump at 115200, this truncates long ACL packets
|
||||||
|
//#define HCI_DUMP_STDOUT_MAX_SIZE_ACL 100
|
||||||
|
|
||||||
|
// enable larger HID descriptor
|
||||||
|
#define MAX_ATTRIBUTE_VALUE_SIZE 512
|
||||||
|
|
||||||
|
#endif // _PICO_BTSTACK_BTSTACK_CONFIG_H
|
||||||
+369
@@ -0,0 +1,369 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
#include "ble/gatt-service/hids_device.h"
|
||||||
|
#include "btstack_ring_buffer.h"
|
||||||
|
#include "btstack_defines.h"
|
||||||
|
|
||||||
|
#include "usb_device.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
|
||||||
|
#include "hid_report.h"
|
||||||
|
|
||||||
|
static uint8_t buffer_storage[REPORT_BUF_SIZE];
|
||||||
|
static btstack_ring_buffer_t report_buf;
|
||||||
|
|
||||||
|
static uint8_t desc_hid_report[HID_DESCRIPTOR_SIZE];
|
||||||
|
static uint16_t desc_hid_report_len=0;
|
||||||
|
static struct report_desc *descriptors;
|
||||||
|
|
||||||
|
static struct report_desc* report_desc_alloc(void);
|
||||||
|
static void report_desc_init(struct report_desc *descriptor);
|
||||||
|
static void report_desc_free(struct report_desc *descriptor);
|
||||||
|
static struct report_desc* report_desc_find(uint8_t dev_addr, uint8_t instance);
|
||||||
|
static struct report_dict* report_dict_alloc(struct report_desc *descriptor);
|
||||||
|
static void report_dict_init(struct report_dict *mapping);
|
||||||
|
static void report_dict_free(struct report_dict *mapping, struct report_desc *descriptor);
|
||||||
|
static struct report_dict* find_mapping(uint8_t dev_addr, uint8_t instance, uint8_t report_id);
|
||||||
|
|
||||||
|
extern hci_con_handle_t con_handle;
|
||||||
|
|
||||||
|
// start listening to HID input reports on all mounted devices
|
||||||
|
bool request_hid_reports_all(void) {
|
||||||
|
// send request to receive reports on all mounted devices
|
||||||
|
struct report_desc * current;
|
||||||
|
for (current=descriptors; current != NULL; current=current->next) {
|
||||||
|
if (! current->listening) {
|
||||||
|
if(request_hid_report(current->dev_addr, current->instance)) {
|
||||||
|
char tempbuf[40];
|
||||||
|
size_t count = sprintf(tempbuf, "Listening to input reports on [%u:%u]\n", current->dev_addr, current->instance);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
current->listening = true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop listening to HID input reports on all mounted devices
|
||||||
|
bool stop_hid_reports_all(void) {
|
||||||
|
// send request to stop reports on all mounted devices
|
||||||
|
struct report_desc * current;
|
||||||
|
for (current=descriptors; current != NULL; current=current->next) {
|
||||||
|
if (current->listening) {
|
||||||
|
if(stop_hid_report(current->dev_addr, current->instance)) {
|
||||||
|
char tempbuf[40];
|
||||||
|
size_t count = sprintf(tempbuf, "Stopping input reports on [%u:%u]\n", current->dev_addr, current->instance);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
current->listening = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to send next HID input report on the BLE interface
|
||||||
|
void send_report(){
|
||||||
|
// process queue and send next report
|
||||||
|
if (btstack_ring_buffer_bytes_available(&report_buf)) {
|
||||||
|
uint8_t dev_addr;
|
||||||
|
uint8_t instance;
|
||||||
|
uint8_t len8[2];
|
||||||
|
uint16_t len;
|
||||||
|
uint32_t num_bytes_read;
|
||||||
|
|
||||||
|
// retrieve dev_addr from ring_buffer
|
||||||
|
btstack_ring_buffer_read(&report_buf, &dev_addr, 1, &num_bytes_read);
|
||||||
|
|
||||||
|
// retrieve instance from ring buffer
|
||||||
|
btstack_ring_buffer_read(&report_buf, &instance, 1, &num_bytes_read);
|
||||||
|
|
||||||
|
// retrieve length as two uint8_t and turn into uint16_t
|
||||||
|
btstack_ring_buffer_read(&report_buf, len8, 2, &num_bytes_read);
|
||||||
|
memcpy(&len, len8, 2);
|
||||||
|
|
||||||
|
// retrieve report from ring buffer
|
||||||
|
uint8_t report[len];
|
||||||
|
btstack_ring_buffer_read(&report_buf, report, len, &num_bytes_read);
|
||||||
|
|
||||||
|
// find report id mapping
|
||||||
|
struct report_dict * mapping = find_mapping(dev_addr, instance, report[0]);
|
||||||
|
|
||||||
|
char tempbuf[16];
|
||||||
|
size_t count=sprintf(tempbuf, "[%04x](%u) ", con_handle, mapping->ble_id);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
|
||||||
|
// send hid report to ble interface
|
||||||
|
if (mapping != NULL) {
|
||||||
|
if (mapping->report_id==0) {
|
||||||
|
hids_device_send_input_report_for_id(con_handle, mapping->ble_id, report, len);
|
||||||
|
cdc_print_hex(report, len);
|
||||||
|
} else {
|
||||||
|
// replace report ID in original report before sending
|
||||||
|
hids_device_send_input_report_for_id(con_handle, mapping->ble_id, &report[1], len-1);
|
||||||
|
cdc_print_hex(&report[1], len-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// request send of next report
|
||||||
|
hids_device_request_can_send_now_event(con_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add report to the BTstack ring buffer for sending
|
||||||
|
void queue_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) {
|
||||||
|
if (con_handle != HCI_CON_HANDLE_INVALID) {
|
||||||
|
// convert len to array of two uint8_t from single uint16_t
|
||||||
|
uint8_t len8[2];
|
||||||
|
memcpy(len8, &len, 2);
|
||||||
|
|
||||||
|
if (btstack_ring_buffer_bytes_free(&report_buf) >= len+4) {
|
||||||
|
// put instance, length, and report into ring buffer if space available
|
||||||
|
btstack_ring_buffer_write(&report_buf, &dev_addr, 1);
|
||||||
|
btstack_ring_buffer_write(&report_buf, &instance, 1);
|
||||||
|
btstack_ring_buffer_write(&report_buf, len8, 2);
|
||||||
|
btstack_ring_buffer_write(&report_buf, report, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// request send on BLE HID interface
|
||||||
|
hids_device_request_can_send_now_event(con_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate memory for HID report ring buffer
|
||||||
|
void init_report_buf(void) {
|
||||||
|
btstack_ring_buffer_init(&report_buf, buffer_storage, sizeof(buffer_storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate memory for USB interface report descriptor
|
||||||
|
static struct report_desc * report_desc_alloc(void) {
|
||||||
|
struct report_desc *ret = REPORT_DESC_ALLOC();
|
||||||
|
|
||||||
|
if (ret != NULL) {
|
||||||
|
report_desc_init(ret);
|
||||||
|
if (descriptors == NULL) {
|
||||||
|
descriptors = ret;
|
||||||
|
} else {
|
||||||
|
struct report_desc *last;
|
||||||
|
for (last = descriptors; last->next != NULL; last=last->next);
|
||||||
|
last->next = ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize report descriptor struct
|
||||||
|
static void report_desc_init(struct report_desc *descriptor) {
|
||||||
|
memset(descriptor, 0, sizeof(struct report_desc));
|
||||||
|
descriptor->next = NULL;
|
||||||
|
descriptor->mappings = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free memory and teardown usb->bt report ID mappings for report descriptor struct
|
||||||
|
static void report_desc_free(struct report_desc *descriptor) {
|
||||||
|
if (descriptor != NULL) {
|
||||||
|
if (descriptors == descriptor) {
|
||||||
|
descriptors = descriptor->next;
|
||||||
|
} else {
|
||||||
|
struct report_desc *last;
|
||||||
|
for (last = descriptors; last->next != NULL; last = last->next) {
|
||||||
|
if ((last->next) == descriptor) {
|
||||||
|
last->next = descriptor->next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (descriptor->mappings != NULL) {
|
||||||
|
report_dict_free(descriptor->mappings, descriptor);
|
||||||
|
}
|
||||||
|
free(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add report descriptor for new HID interface
|
||||||
|
bool add_descriptor(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) {
|
||||||
|
struct report_desc * descriptor = report_desc_alloc();
|
||||||
|
if (descriptor == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(descriptor->descriptor, desc_report, desc_len);
|
||||||
|
descriptor->desc_len = desc_len;
|
||||||
|
descriptor->dev_addr = dev_addr;
|
||||||
|
descriptor->instance = instance;
|
||||||
|
descriptor->listening = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate new report descriptor for BLE device and save mappings for report IDs
|
||||||
|
bool generate_report_descriptor(void) {
|
||||||
|
memset(desc_hid_report, 0, sizeof(desc_hid_report));
|
||||||
|
desc_hid_report_len=0;
|
||||||
|
|
||||||
|
uint8_t num_mounted = 0;
|
||||||
|
|
||||||
|
struct report_desc * current;
|
||||||
|
for (current=descriptors; current != NULL; current=current->next) {
|
||||||
|
if (desc_hid_report_len + (current->desc_len) + 2 < HID_DESCRIPTOR_SIZE) {
|
||||||
|
memcpy(&desc_hid_report[desc_hid_report_len], current->descriptor, current->desc_len);
|
||||||
|
} else {
|
||||||
|
cdc_print_msg("Err: HID report descriptor too long\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool no_id = true;
|
||||||
|
|
||||||
|
// search for report ID (0x85) in report descriptor
|
||||||
|
for (int i=0; i<(current->desc_len); i++) {
|
||||||
|
if(current->descriptor[i] == 0x85) {
|
||||||
|
// use report ID found in descriptor
|
||||||
|
no_id=false;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// modify report ID and save mapping
|
||||||
|
struct report_dict * report_map = report_dict_alloc(current);
|
||||||
|
if (report_map == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
num_mounted++;
|
||||||
|
report_map->report_id = current->descriptor[i];
|
||||||
|
report_map->ble_id = num_mounted;
|
||||||
|
|
||||||
|
desc_hid_report[desc_hid_report_len+i]=num_mounted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no report ID found in descriptor for the interface
|
||||||
|
if (no_id) {
|
||||||
|
// add report id field to beginning of descriptor after Collection (0xa1) field
|
||||||
|
uint8_t col_pos=0;
|
||||||
|
for (int i=0; i<(current->desc_len); i++) {
|
||||||
|
if(current->descriptor[i] == 0xa1) {
|
||||||
|
col_pos=i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
desc_hid_report[desc_hid_report_len+col_pos+2] = 0x85;
|
||||||
|
desc_hid_report[desc_hid_report_len+col_pos+3] = num_mounted+1;
|
||||||
|
memcpy(&desc_hid_report[desc_hid_report_len+col_pos+4], ¤t->descriptor[col_pos+2], current->desc_len-col_pos);
|
||||||
|
desc_hid_report_len += current->desc_len+2;
|
||||||
|
|
||||||
|
// store mapping with report ID of 0
|
||||||
|
struct report_dict * report_map = report_dict_alloc(current);
|
||||||
|
num_mounted++;
|
||||||
|
report_map->report_id = 0;
|
||||||
|
report_map->ble_id = num_mounted;
|
||||||
|
} else {
|
||||||
|
desc_hid_report_len += current->desc_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_mounted > NUM_REPORT_IDS) {
|
||||||
|
cdc_print_msg("Error: too many report IDs\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
btstack_ring_buffer_reset(&report_buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove report descriptor for HID interface
|
||||||
|
void remove_instance(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
struct report_desc *descriptor = report_desc_find(dev_addr, instance);
|
||||||
|
|
||||||
|
if (descriptor != NULL) {
|
||||||
|
report_desc_free(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find report descriptor by device address and instance
|
||||||
|
static struct report_desc * report_desc_find(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
struct report_desc *descriptor;
|
||||||
|
for (descriptor = descriptors; descriptor != NULL; descriptor = descriptor->next) {
|
||||||
|
if (descriptor->dev_addr==dev_addr && descriptor->instance==instance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate memory for usb->bt report id mapping
|
||||||
|
static struct report_dict * report_dict_alloc(struct report_desc * descriptor) {
|
||||||
|
struct report_dict *ret = REPORT_DICT_ALLOC();
|
||||||
|
|
||||||
|
if (ret != NULL) {
|
||||||
|
report_dict_init(ret);
|
||||||
|
if (descriptor->mappings == NULL) {
|
||||||
|
descriptor->mappings = ret;
|
||||||
|
} else {
|
||||||
|
struct report_dict *last;
|
||||||
|
for (last = descriptor->mappings; last->next != NULL; last=last->next);
|
||||||
|
last->next = ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize usb->bt report id mapping struct
|
||||||
|
static void report_dict_init(struct report_dict *mapping) {
|
||||||
|
memset(mapping, 0, sizeof(struct report_dict));
|
||||||
|
mapping->next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free memory from report id mapping
|
||||||
|
static void report_dict_free(struct report_dict *mapping, struct report_desc *descriptor) {
|
||||||
|
if (mapping != NULL) {
|
||||||
|
if (descriptor->mappings == mapping) {
|
||||||
|
descriptor->mappings = mapping->next;
|
||||||
|
} else {
|
||||||
|
struct report_dict *last;
|
||||||
|
for (last = descriptor->mappings; last->next != NULL; last = last->next) {
|
||||||
|
if ((last->next) == mapping) {
|
||||||
|
last->next = mapping->next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the usb->bt report id mapping struct for given device, instance, and usb report id
|
||||||
|
static struct report_dict * find_mapping(uint8_t dev_addr, uint8_t instance, uint8_t report_id) {
|
||||||
|
struct report_desc * descriptor;
|
||||||
|
descriptor = report_desc_find(dev_addr, instance);
|
||||||
|
|
||||||
|
if (descriptor != NULL) {
|
||||||
|
struct report_dict * mapping;
|
||||||
|
for (mapping = descriptor->mappings; mapping != NULL; mapping = mapping->next) {
|
||||||
|
if (mapping->report_id ==0 || mapping->report_id == report_id) {
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_desc_hid_report_len(void) {
|
||||||
|
return desc_hid_report_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t const* get_desc_hid_report(void) {
|
||||||
|
return desc_hid_report;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef HID_REPORT_H_
|
||||||
|
#define HID_REPORT_H_
|
||||||
|
|
||||||
|
#define NUM_REPORT_IDS 16
|
||||||
|
#define REPORT_BUF_SIZE 256
|
||||||
|
#define DESCRIPTOR_BUF_SIZE 256
|
||||||
|
#define HID_DESCRIPTOR_SIZE 512
|
||||||
|
|
||||||
|
struct report_desc {
|
||||||
|
uint8_t dev_addr;
|
||||||
|
uint8_t instance;
|
||||||
|
uint8_t descriptor[DESCRIPTOR_BUF_SIZE];
|
||||||
|
uint16_t desc_len;
|
||||||
|
struct report_desc *next;
|
||||||
|
struct report_dict *mappings;
|
||||||
|
bool listening;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct report_dict {
|
||||||
|
uint8_t report_id;
|
||||||
|
uint8_t ble_id;
|
||||||
|
struct report_dict *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
# define REPORT_DESC_ALLOC() (struct report_desc *)malloc(sizeof(struct report_desc))
|
||||||
|
# define REPORT_DICT_ALLOC() (struct report_dict *)malloc(sizeof(struct report_dict))
|
||||||
|
|
||||||
|
bool request_hid_reports_all(void);
|
||||||
|
bool stop_hid_reports_all(void);
|
||||||
|
void send_report();
|
||||||
|
void queue_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
|
||||||
|
void init_report_buf(void);
|
||||||
|
bool add_descriptor(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len);
|
||||||
|
void remove_instance(uint8_t dev_addr, uint8_t instance);
|
||||||
|
bool generate_report_descriptor(void);
|
||||||
|
uint16_t get_desc_hid_report_len(void);
|
||||||
|
uint8_t const* get_desc_hid_report(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "pico/bootrom.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "pio_usb.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
|
||||||
|
#include "hid_report.h"
|
||||||
|
#include "bt_device.h"
|
||||||
|
#include "usb_device.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
|
||||||
|
// main loop
|
||||||
|
int main(void) {
|
||||||
|
|
||||||
|
set_sys_clock_khz(192000, true);
|
||||||
|
|
||||||
|
sleep_ms(10);
|
||||||
|
|
||||||
|
// setup BLE on core 1
|
||||||
|
multicore_reset_core1();
|
||||||
|
multicore_launch_core1(btstack_main);
|
||||||
|
|
||||||
|
// init and run usb host
|
||||||
|
usb_host_init();
|
||||||
|
|
||||||
|
// init and run usb device
|
||||||
|
usb_device_init();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch ( get_host_state() ) {
|
||||||
|
case HOST_NEW_DESCRIPTOR:
|
||||||
|
if ( host_ready() ) {
|
||||||
|
if(generate_report_descriptor()) {
|
||||||
|
if ( get_desc_hid_report_len() > 0 ) {
|
||||||
|
set_host_state(HOST_MOUNTED);
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
|
||||||
|
} else {
|
||||||
|
set_host_state(HOST_INACTIVE);
|
||||||
|
}
|
||||||
|
cdc_print_msg("Updating HID report map\n");
|
||||||
|
update_desc_hid_report();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HOST_START_LISTEN:
|
||||||
|
if (request_hid_reports_all()) {
|
||||||
|
set_host_state(HOST_LISTENING);
|
||||||
|
} else {
|
||||||
|
cdc_print_msg("Error listening to input report(s)\n");
|
||||||
|
set_host_state(HOST_INACTIVE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HOST_STOP_LISTEN:
|
||||||
|
if (stop_hid_reports_all()) {
|
||||||
|
set_host_state(HOST_INACTIVE);
|
||||||
|
} else {
|
||||||
|
cdc_print_msg("Error stopping input report(s)\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tuh_task(); // tinyusb host task
|
||||||
|
tud_task(); // tinyusb device task
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TUSB_CONFIG_H_
|
||||||
|
#define _TUSB_CONFIG_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// COMMON CONFIGURATION
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_OS
|
||||||
|
#define CFG_TUSB_OS OPT_OS_NONE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Enable device stack
|
||||||
|
#define CFG_TUD_ENABLED 1
|
||||||
|
|
||||||
|
// Enable host stack
|
||||||
|
#define CFG_TUH_ENABLED 1
|
||||||
|
#define CFG_TUH_RPI_PIO_USB 1
|
||||||
|
|
||||||
|
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
|
||||||
|
// #define CFG_TUSB_DEBUG 0
|
||||||
|
|
||||||
|
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
|
||||||
|
* Tinyusb use follows macros to declare transferring memory so that they can be put
|
||||||
|
* into those specific section.
|
||||||
|
* e.g
|
||||||
|
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||||
|
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||||
|
*/
|
||||||
|
#ifndef CFG_TUSB_MEM_SECTION
|
||||||
|
#define CFG_TUSB_MEM_SECTION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_MEM_ALIGN
|
||||||
|
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// DEVICE CONFIGURATION
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
//------------------------- Board Specific --------------------------
|
||||||
|
|
||||||
|
// RHPort number used for device can be defined by board.mk, default to port 0
|
||||||
|
#ifndef BOARD_TUD_RHPORT
|
||||||
|
#define BOARD_TUD_RHPORT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// RHPort max operational speed can defined by board.mk
|
||||||
|
#ifndef BOARD_TUD_MAX_SPEED
|
||||||
|
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CFG_TUD_ENDPOINT0_SIZE
|
||||||
|
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//------------- Driver configuration -------------//
|
||||||
|
#define CFG_TUD_CDC 1
|
||||||
|
|
||||||
|
// CDC FIFO size of TX and RX
|
||||||
|
#define CFG_TUD_CDC_RX_BUFSIZE 32
|
||||||
|
#define CFG_TUD_CDC_TX_BUFSIZE 64
|
||||||
|
|
||||||
|
// CDC Endpoint transfer buffer size, more is faster
|
||||||
|
#define CFG_TUD_CDC_EP_BUFSIZE 64
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// HOST CONFIGURATION
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
//------------------------- Board Specific --------------------------
|
||||||
|
|
||||||
|
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||||
|
#ifndef BOARD_TUH_RHPORT
|
||||||
|
#define BOARD_TUH_RHPORT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// RHPort max operational speed can defined by board.mk
|
||||||
|
#ifndef BOARD_TUH_MAX_SPEED
|
||||||
|
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-----------------------Driver configuration-------------------------
|
||||||
|
|
||||||
|
// Size of buffer to hold descriptors and other data used for enumeration
|
||||||
|
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||||
|
|
||||||
|
#define CFG_TUH_HUB 1
|
||||||
|
// Default is max speed that hardware controller could support with on-chip PHY
|
||||||
|
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||||
|
// max device support (excluding hub device)
|
||||||
|
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB*3+1) // hub typically has 4 ports
|
||||||
|
|
||||||
|
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX+1)
|
||||||
|
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||||
|
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _TUSB_CONFIG_H_ */
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||||
|
* sekigon-gonnoc
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "bsp/board_api.h"
|
||||||
|
|
||||||
|
#include "usb_descriptors.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// Device Descriptors
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
tusb_desc_device_t const desc_device =
|
||||||
|
{
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = USB_BCD,
|
||||||
|
|
||||||
|
// Use Interface Association Descriptor (IAD) for CDC
|
||||||
|
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
|
||||||
|
.bDeviceClass = TUSB_CLASS_MISC,
|
||||||
|
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||||
|
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||||
|
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
|
||||||
|
.idVendor = USB_VID,
|
||||||
|
.idProduct = USB_PID,
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
|
||||||
|
.iManufacturer = 0x01,
|
||||||
|
.iProduct = 0x02,
|
||||||
|
.iSerialNumber = 0x03,
|
||||||
|
|
||||||
|
.bNumConfigurations = 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invoked when received GET DEVICE DESCRIPTOR
|
||||||
|
// Application return pointer to descriptor
|
||||||
|
uint8_t const * tud_descriptor_device_cb(void)
|
||||||
|
{
|
||||||
|
return (uint8_t const *) &desc_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// Configuration Descriptor
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// full speed configuration
|
||||||
|
uint8_t const desc_fs_configuration[] =
|
||||||
|
{
|
||||||
|
// Config number, interface count, string index, total length, attribute, power in mA
|
||||||
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
|
||||||
|
|
||||||
|
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
|
||||||
|
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||||
|
// Application return pointer to descriptor
|
||||||
|
// Descriptor contents must exist long enough for transfer to complete
|
||||||
|
uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
|
||||||
|
{
|
||||||
|
(void) index; // for multiple configurations
|
||||||
|
return desc_fs_configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// String Descriptors
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// array of pointer to string descriptors
|
||||||
|
char const* string_desc_arr [] =
|
||||||
|
{
|
||||||
|
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
|
||||||
|
"Raspberry Pi", // 1: Manufacturer
|
||||||
|
"Pico BLE HID", // 2: Product
|
||||||
|
NULL, // 3: Serials, should use chip ID
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint16_t _desc_str[32+1];
|
||||||
|
|
||||||
|
// Invoked when received GET STRING DESCRIPTOR request
|
||||||
|
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
|
||||||
|
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||||
|
{
|
||||||
|
(void) langid;
|
||||||
|
|
||||||
|
uint8_t chr_count;
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 0: // langid
|
||||||
|
memcpy(&_desc_str[1], string_desc_arr[0], 2);
|
||||||
|
chr_count = 1;
|
||||||
|
break;
|
||||||
|
case 3: // serial
|
||||||
|
chr_count = board_usb_get_serial(_desc_str+1, 32);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
|
||||||
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
|
||||||
|
|
||||||
|
if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
|
||||||
|
|
||||||
|
char* str = string_desc_arr[index];
|
||||||
|
|
||||||
|
// Cap at max char
|
||||||
|
chr_count = (uint8_t) strlen(str);
|
||||||
|
if ( chr_count > 31 ) chr_count = 31;
|
||||||
|
|
||||||
|
// Convert ASCII string into UTF-16
|
||||||
|
for(uint8_t i=0; i<chr_count; i++) {
|
||||||
|
_desc_str[1+i] = str[i];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first byte is length (including header), second byte is string type
|
||||||
|
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
|
||||||
|
|
||||||
|
return _desc_str;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef USB_DESCRIPTORS_H_
|
||||||
|
#define USB_DESCRIPTORS_H_
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ITF_NUM_CDC=0,
|
||||||
|
ITF_NUM_CDC_DATA,
|
||||||
|
ITF_NUM_TOTAL
|
||||||
|
};
|
||||||
|
|
||||||
|
#define USB_PID 0xB1ED
|
||||||
|
#define USB_VID 0xCEC0
|
||||||
|
#define USB_BCD 0x0200
|
||||||
|
|
||||||
|
#define EPNUM_CDC_NOTIF 0x81
|
||||||
|
#define EPNUM_CDC_OUT 0x02
|
||||||
|
#define EPNUM_CDC_IN 0x82
|
||||||
|
|
||||||
|
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)
|
||||||
|
|
||||||
|
#endif /* USB_DESCRIPTORS_H_ */
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "usb_descriptors.h"
|
||||||
|
|
||||||
|
#include "usb_device.h"
|
||||||
|
|
||||||
|
void usb_device_init(void) {
|
||||||
|
// run TinyUSB device
|
||||||
|
tusb_rhport_init_t dev_init = {
|
||||||
|
.role = TUSB_ROLE_DEVICE,
|
||||||
|
.speed = TUSB_SPEED_AUTO,
|
||||||
|
};
|
||||||
|
tusb_init(BOARD_TUH_RHPORT, &dev_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received SET_REPORT control request or
|
||||||
|
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
||||||
|
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
|
||||||
|
(void) instance;
|
||||||
|
(void) report_id;
|
||||||
|
(void) report_type;
|
||||||
|
(void) buffer;
|
||||||
|
(void) bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received GET_REPORT control request
|
||||||
|
// Application must fill buffer report's content and return its length.
|
||||||
|
// Return zero will cause the stack to STALL request
|
||||||
|
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
|
||||||
|
{
|
||||||
|
(void) instance;
|
||||||
|
(void) report_id;
|
||||||
|
(void) report_type;
|
||||||
|
(void) buffer;
|
||||||
|
(void) reqlen;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print message to CDC in raw hex
|
||||||
|
void cdc_print_hex(uint8_t const* msg, uint16_t msg_len) {
|
||||||
|
char tempbuf[8];
|
||||||
|
size_t count;
|
||||||
|
for (int i=0; i<msg_len; i++) {
|
||||||
|
count=sprintf(tempbuf, "%02X ", msg[i]);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
}
|
||||||
|
tud_cdc_write_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// print text message to CDC
|
||||||
|
void cdc_print_str(char const* msg, uint16_t msg_len) {
|
||||||
|
tud_cdc_write(msg, msg_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cdc_print_msg(char const* msg) {
|
||||||
|
uint16_t msg_len = strlen(msg);
|
||||||
|
cdc_print_str(msg, msg_len);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef MAIN_DEVICE_H_
|
||||||
|
#define MAIN_DEVICE_H_
|
||||||
|
|
||||||
|
void usb_device_init(void);
|
||||||
|
void cdc_print_hex(uint8_t const* msg, uint16_t msg_len);
|
||||||
|
void cdc_print_str(char const* msg, uint16_t msg_len);
|
||||||
|
void cdc_print_msg(char const* msg);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pio_usb.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
|
||||||
|
#include "hid_report.h"
|
||||||
|
#include "usb_device.h"
|
||||||
|
|
||||||
|
#include "usb_host.h"
|
||||||
|
|
||||||
|
static host_state_t host_state;
|
||||||
|
static absolute_time_t request_time;
|
||||||
|
|
||||||
|
// initialize usb host
|
||||||
|
void usb_host_init(void) {
|
||||||
|
// configure PIO USB for TinyUSB host
|
||||||
|
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
|
||||||
|
pio_cfg.alarm_pool = (void*) alarm_pool_create(2,1);
|
||||||
|
tuh_configure(1, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg);
|
||||||
|
|
||||||
|
// run TinyUSB host
|
||||||
|
tusb_rhport_init_t host_init = {
|
||||||
|
.role = TUSB_ROLE_HOST,
|
||||||
|
.speed = TUSB_SPEED_AUTO,
|
||||||
|
};
|
||||||
|
tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT);
|
||||||
|
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||||
|
|
||||||
|
set_host_state(HOST_INACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is mounted
|
||||||
|
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
|
||||||
|
{
|
||||||
|
uint16_t vid, pid;
|
||||||
|
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
|
||||||
|
/// send device vid:pid information to CDC for debugging
|
||||||
|
char tempbuf[CFG_TUD_CDC_TX_BUFSIZE];
|
||||||
|
size_t count = sprintf(tempbuf, "Mount: [%04x:%04x][%u:%u] Protocol = %u\n", vid, pid, dev_addr, instance, itf_protocol);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
|
||||||
|
// print descriptor
|
||||||
|
//cdc_print_hex(desc_report, desc_len);
|
||||||
|
|
||||||
|
// add to HID report descriptor
|
||||||
|
if ( add_descriptor(dev_addr, instance, desc_report, desc_len)) {
|
||||||
|
set_host_state(HOST_NEW_DESCRIPTOR);
|
||||||
|
request_time=get_absolute_time();
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is un-mounted
|
||||||
|
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
|
||||||
|
{
|
||||||
|
// send device address:instance to CDC for debugging
|
||||||
|
char tempbuf[CFG_TUD_CDC_TX_BUFSIZE];
|
||||||
|
size_t count = sprintf(tempbuf, "Unmount: [%u:%u]\n", dev_addr, instance);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
|
||||||
|
if (stop_hid_report(dev_addr, instance)) {
|
||||||
|
cdc_print_msg("Successfully stopped receiving reports\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_instance(dev_addr, instance);
|
||||||
|
set_host_state(HOST_NEW_DESCRIPTOR);
|
||||||
|
request_time=get_absolute_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received report from device via interrupt endpoint
|
||||||
|
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
|
||||||
|
{
|
||||||
|
char tempbuf[32];
|
||||||
|
size_t count = sprintf(tempbuf, "[%04x:%04x](%u) ", dev_addr, instance, len);
|
||||||
|
cdc_print_str(tempbuf,count);
|
||||||
|
cdc_print_hex(report, len);
|
||||||
|
|
||||||
|
queue_report(dev_addr, instance, report, len);
|
||||||
|
|
||||||
|
// continue to request to receive report
|
||||||
|
if ( !tuh_hid_receive_report(dev_addr, instance) )
|
||||||
|
{
|
||||||
|
cdc_print_msg("Error: cannot request report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the state of the host
|
||||||
|
void set_host_state(host_state_t new_host_state) {
|
||||||
|
host_state = new_host_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the current state of the host
|
||||||
|
host_state_t get_host_state(void) {
|
||||||
|
return host_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// indicate whether host is ready for updating descriptors
|
||||||
|
bool host_ready(void) {
|
||||||
|
return (absolute_time_diff_us(request_time, get_absolute_time()) > 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// request HID input reports on specified device address and instance
|
||||||
|
bool request_hid_report(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
// request to receive reports HID devices
|
||||||
|
if ( !tuh_hid_receive_report(dev_addr, instance) ) {
|
||||||
|
char tempbuf[CFG_TUD_CDC_TX_BUFSIZE];
|
||||||
|
size_t count = sprintf(tempbuf, "Error: cannot request report on [%u:%u]\n", dev_addr, instance);
|
||||||
|
cdc_print_str(tempbuf, count);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop receiving HID input reports on specified device address and instance
|
||||||
|
bool stop_hid_report(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
if (!tuh_hid_receive_abort(dev_addr, instance)) {
|
||||||
|
cdc_print_msg("Error: could not stop receiving reports\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef MAIN_HOST_H_
|
||||||
|
#define MAIN_HOST_H_
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HOST_INACTIVE=0,
|
||||||
|
HOST_NEW_DESCRIPTOR,
|
||||||
|
HOST_MOUNTED,
|
||||||
|
HOST_START_LISTEN,
|
||||||
|
HOST_LISTENING,
|
||||||
|
HOST_STOP_LISTEN,
|
||||||
|
} host_state_t;
|
||||||
|
|
||||||
|
|
||||||
|
void usb_host_init(void);
|
||||||
|
void set_host_state(host_state_t new_host_state);
|
||||||
|
host_state_t get_host_state(void);
|
||||||
|
bool host_ready(void);
|
||||||
|
bool request_hid_report(uint8_t dev_addr, uint8_t instance);
|
||||||
|
bool stop_hid_report(uint8_t dev_addr, uint8_t instance);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
Reference in New Issue
Block a user