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