initial commit

This commit is contained in:
2025-08-13 06:37:47 -04:00
commit 77fe11c758
19 changed files with 1728 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build/**/*
+49
View File
@@ -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})
+15
View File
@@ -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/>.
+166
View File
@@ -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.
+99
View File
@@ -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
+89
View File
@@ -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
View File
@@ -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);
}
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef BT_DEVICE_H_
#define BT_DEVICE_H_
void btstack_main(void);
void update_desc_hid_report(void);
#endif
+92
View File
@@ -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
View File
@@ -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], &current->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;
}
+39
View File
@@ -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
+72
View File
@@ -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
View File
@@ -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_ */
+146
View File
@@ -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;
}
+21
View File
@@ -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_ */
+64
View File
@@ -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);
}
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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