commit 77fe11c758376c70967385c8529db915bfc21041 Author: kenji Date: Wed Aug 13 06:37:47 2025 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5f1b11 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/**/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d886277 --- /dev/null +++ b/CMakeLists.txt @@ -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}) + diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000..f0aa6ac --- /dev/null +++ b/COPYING.LESSER @@ -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 . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5357f69 --- /dev/null +++ b/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a396b3 --- /dev/null +++ b/README.md @@ -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 diff --git a/ble_hid.gatt b/ble_hid.gatt new file mode 100644 index 0000000..162c79c --- /dev/null +++ b/ble_hid.gatt @@ -0,0 +1,89 @@ +PRIMARY_SERVICE, GAP_SERVICE +CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Pico BLE HID" + +// add Battery Service +#import + +// add Device ID Service +#import + +// 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, + diff --git a/bt_device.c b/bt_device.c new file mode 100644 index 0000000..a2557e2 --- /dev/null +++ b/bt_device.c @@ -0,0 +1,205 @@ +#define BTSTACK_FILE__ "bt_device.c" + +#include +#include +#include +#include +#include + +#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); + } +} diff --git a/bt_device.h b/bt_device.h new file mode 100644 index 0000000..6dfeedd --- /dev/null +++ b/bt_device.h @@ -0,0 +1,7 @@ +#ifndef BT_DEVICE_H_ +#define BT_DEVICE_H_ + +void btstack_main(void); +void update_desc_hid_report(void); + +#endif diff --git a/btstack_config.h b/btstack_config.h new file mode 100644 index 0000000..45206c4 --- /dev/null +++ b/btstack_config.h @@ -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 diff --git a/hid_report.c b/hid_report.c new file mode 100644 index 0000000..d00e00f --- /dev/null +++ b/hid_report.c @@ -0,0 +1,369 @@ +#include +#include +#include +#include + +#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; +} diff --git a/hid_report.h b/hid_report.h new file mode 100644 index 0000000..7dccb5d --- /dev/null +++ b/hid_report.h @@ -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 diff --git a/main.c b/main.c new file mode 100644 index 0000000..c0695b4 --- /dev/null +++ b/main.c @@ -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; +} diff --git a/tusb_config.h b/tusb_config.h new file mode 100644 index 0000000..48266f1 --- /dev/null +++ b/tusb_config.h @@ -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_ */ diff --git a/usb_descriptors.c b/usb_descriptors.c new file mode 100644 index 0000000..a82ae5b --- /dev/null +++ b/usb_descriptors.c @@ -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 +#include +#include + +#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 +#include +#include +#include + +#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; +} diff --git a/usb_host.h b/usb_host.h new file mode 100644 index 0000000..eefd6ba --- /dev/null +++ b/usb_host.h @@ -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 +