Created
January 5, 2024 13:50
-
-
Save sonnny/06a95a17ed4041a2a5d03ad5038ef173 to your computer and use it in GitHub Desktop.
demo of pico w bluetooth using an nes gamepad, demo of pio irq, send signal to program from pio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| demo of nes gamepad using pico w bluetooth | |
| source from https://codeberg.org/chipfire/rppico-bt-gamepad | |
| /**************************** | |
| * bt_gamepad.c | |
| **********************/ | |
| /** | |
| * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| #include <stdio.h> | |
| #include "btstack.h" | |
| #include "pico/cyw43_arch.h" | |
| #include "pico/btstack_cyw43.h" | |
| #include "pico/stdlib.h" | |
| #include "server_common.h" | |
| #include "nes_gamepad.h" | |
| // this is the same report descriptor used by the gamepad USB example, | |
| // with an added report ID which USB doesn't demand in case there's only one report. | |
| uint8_t const reportDescriptor[] = { | |
| 0x05, 0x01,// Usage Page(Generic Desktop) | |
| 0x09, 0x05,// Usage(Gamepad) | |
| 0xa1, 0x01,// Collection(Application) | |
| 0x85, 0x01,// ReportID(1) | |
| 0xa1, 0x00,// Collection(Physical) | |
| 0x75, 0x08,// Report Size(8) | |
| 0x95, 0x02,// Report Count(2) | |
| 0x15, 0x81,// Logical Minimum(-127) | |
| 0x25, 0x7f,// Logical Maximum(127) | |
| 0x19, 0x30,// Usage Minimum(X) | |
| 0x29, 0x31,// Usage Maximum(Y) | |
| 0x81, 0x02,// Input(Data, Variable, Absolut) | |
| 0x75, 0x01,// Report Size(1) | |
| 0x95, 0x02,// Report Count(2) | |
| 0x15, 0x00,// Logical Minimum(0) | |
| 0x25, 0x01,// Logical Maximum(1) | |
| 0x19, 0x3d,// Usage Minimum(Start) | |
| 0x29, 0x3e,// Usage Maximum(Select) | |
| 0x81, 0x02,// Input(Data, Variable, Absolute) | |
| 0x05, 0x09,// Usage Page(Button) | |
| 0x19, 0x01,// Usage Minimum(Button 1) | |
| 0x29, 0x02,// Usage Maximum(Button 2) | |
| 0x81, 0x02,// Input(Data, Variable, Absolute) | |
| 0x75, 0x01,// Report Size(4) | |
| 0x95, 0x02,// Report Count(1) | |
| 0x81, 0x03,// Input(Constant, Variable) | |
| 0xc0,// End Collection | |
| 0xc0// End Collection | |
| }; | |
| // data structures for callback handlers | |
| static btstack_packet_callback_registration_t hci_event_callback_registration; | |
| static btstack_packet_callback_registration_t sm_event_callback_registration; | |
| uint8_t report[] = {0x00, 0x00, 0x00}; | |
| size_t const reportSize = 3; | |
| // NOTE: I've never seen suspend being used so far | |
| bool inSuspend = false; | |
| // turn on/off LED to indicate suspend | |
| void suspendNotification(int suspended) { | |
| cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, !suspended); | |
| inSuspend = !suspended; | |
| } | |
| int main() { | |
| uint gpState = 0xff, gpStateLast = 0; | |
| stdio_init_all(); | |
| // remove comment to get some more time to attach your serial console | |
| //sleep_ms(2000); | |
| // initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1) | |
| if (cyw43_arch_init()) { | |
| printf("failed to initialise cyw43_arch\n"); | |
| return -1; | |
| } | |
| // start gamepad reading code which uses programmable IO | |
| if(gamepadStart(pio0, 8, 6, 7, &gpState) != 0) { | |
| printf("Oops: failed to initialize gamepad!\n"); | |
| return -1; | |
| } | |
| printf("init done, starting bluetooth stack.\n"); | |
| // init Btstack | |
| l2cap_init(); | |
| // init state machine and configure state machine to handle bonding | |
| sm_init(); | |
| sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); | |
| sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING); | |
| // initialize ATT server, but don't set read and write callback functions yet. | |
| // This is done afterwards in gamepadBtInit(). | |
| // profile_data comes from bt_nes_gamepad.h which is generated by Btstack. | |
| att_server_init(profile_data, NULL, NULL); | |
| // inform about BTstack state | |
| hci_event_callback_registration.callback = &packet_handler; | |
| hci_add_event_handler(&hci_event_callback_registration); | |
| // register for SM events | |
| sm_event_callback_registration.callback = &packet_handler; | |
| sm_add_event_handler(&sm_event_callback_registration); | |
| // packet handler is registered by the HID code. | |
| // initialize the HID handler. | |
| gamepadBtInit(reportDescriptor, sizeof(reportDescriptor), suspendNotification); | |
| // turn on bluetooth! | |
| hci_power_control(HCI_POWER_ON); | |
| // in this loop, we check whether input data has changed. If so, we notify the client, if one is connected. | |
| while(true) { | |
| sleep_ms(20); | |
| if(gpState != ~gpStateLast) { | |
| // we need to invert gpState since the NES gamepad buttons report HIGH when not pressed | |
| gpStateLast = ~gpState; | |
| // prepare the report, then tell Btstack we want to send a notification. | |
| // When sending HID reports using HID over GATT profile, we MUST NOT include the report ID (which would go in Byte 0, data | |
| // items would follow: B1 = X, B2 = Y, B3 = buttons; This format is used when transmitting the report over USB). | |
| // The report ID is added by the report host's Bluetooth stack, see HID over GATT spec, section 4.8.1 | |
| report[0] = (gpStateLast & 0x01) ? 0x7f : ((gpStateLast & 0x02) ? 0x81 : 0x00);// X axis; bit 0 is right, bit 1 left | |
| report[1] = (gpStateLast & 0x04) ? 0x7f : ((gpStateLast & 0x08) ? 0x81 : 0x00);// Y axis; bit 3 is down, bit 2 is up | |
| // start and select are reported separately in bits 0 and 1, A and B in bits 2 and 3. | |
| report[2] = ((gpStateLast & 0x10) >> 4) | ((gpStateLast & 0x20) >> 4) | ((gpStateLast & 0x80) >> 5) | ((gpStateLast & 0x40) >> 3); | |
| // transmit the report we just generated. requestReportTransmission() checks whether | |
| // notifications are enabled, in case they're not, nothing happens. | |
| // if(!inSuspend) | |
| requestReportTransmission(); | |
| } | |
| } | |
| return 0; | |
| } | |
| /**************************** | |
| * nes_gamepad.c | |
| **********************/ | |
| #include <stdio.h> | |
| #include "hardware/gpio.h" | |
| #include "hardware/pio.h" | |
| #include "hardware/irq.h" | |
| #include "build/nes_gamepad.pio.h" | |
| uint pioProgOffset; | |
| int pioSm; | |
| PIO pioUsed; | |
| uint *gpState; | |
| void gamepadIrqHandler() { | |
| // the interrupt handler, not much exiting stuff going on here. | |
| // We just need to clear the interrupt and read the new data from the SM FIFO. | |
| // clear interrupt first | |
| pio_interrupt_clear(pioUsed, 0); | |
| // then read current status word from SM FIFO and write it to the configured destination. | |
| *gpState = pio_sm_get(pioUsed, pioSm); | |
| } | |
| int gamepadStart(PIO pio, uint dataPin, uint clkPin, uint psPin, uint *gpStateVar) { | |
| gpState = NULL; | |
| if(gpStateVar == NULL) | |
| return -1; | |
| // try to get a PIO SM, returns -1 if none is available. | |
| pioSm = pio_claim_unused_sm(pio, false); | |
| // if no SM was available, fail | |
| if(pioSm < 0) | |
| return -1; | |
| // check whether the PIO has sufficient program space available. If not, fail. | |
| if(!pio_can_add_program(pio, &NES_controller_interface_program)) | |
| return -1; | |
| // for debugging | |
| // printf("Pins assigned: %u %u %u SM used: %d\n", dataPin, clkPin, psPin, pioSm); | |
| pioUsed = pio; | |
| gpState = gpStateVar; | |
| pioProgOffset = pio_add_program(pio, &NES_controller_interface_program); | |
| nesControllerProgramInit(pio, (uint) pioSm, pioProgOffset, dataPin, clkPin, psPin); | |
| // set up interrupt handler and source. Making the IRQ configurable would be nice. | |
| irq_set_exclusive_handler(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, gamepadIrqHandler); | |
| irq_set_enabled(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, true); | |
| irq_set_priority(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, PICO_LOWEST_IRQ_PRIORITY); | |
| pio_set_irq0_source_enabled(pio, pis_interrupt0 + pioSm, true); | |
| // enable the SM, that also starts the program | |
| pio_sm_set_enabled(pio, pioSm, true); | |
| return 0; | |
| } | |
| // can be used for debugging | |
| uint getPioProgOff() { | |
| return pioProgOffset; | |
| } | |
| uint getPioSm() { | |
| return pioSm; | |
| } | |
| /***************************** | |
| * nes_gamepad.h | |
| ****************************/ | |
| #include "hardware/pio.h" | |
| #ifndef NES_GAMEPAD_H__ | |
| #define NES_GAMEPAD_H__ | |
| int gamepadStart(PIO pio, uint dataPin, uint clkPin, uint psPin, uint *gpStateVar); | |
| uint getPioProgOff(); | |
| uint getPioSm(); | |
| #endif | |
| /******************************** | |
| * nes_gamepad.pio | |
| *****************************/ | |
| .program NES_controller_interface | |
| .side_set 1 | |
| ; every instruction ins delayed another three cycles so we can get a really slow clock. | |
| .wrap_target | |
| ; first assert P/S and toggle the clock once to latch data | |
| set PINS, 1 side 0 [3] | |
| ; set counter register | |
| set X, 7 side 1 [3] | |
| ; deassert P/S to switch to shift register mode | |
| set PINS, 0 side 1 [3] | |
| readBits: | |
| ; read one bit, then jump back until we've read 8 | |
| in PINS, 1 side 0 [3] | |
| jmp X-- readBits side 1 [3] | |
| ; notify CPU that new data has arrived | |
| irq 0 side 1 [3] | |
| .wrap | |
| % c-sdk { | |
| static inline void nesControllerProgramInit(PIO pio, uint sm, uint offset, uint dataPin, uint clkPin, uint psPin) { | |
| // initialize GPIO pins. This changes their function to PIO. | |
| // The pins are used as follows: | |
| // dataPin is only read from (in) | |
| // clkPin is controlled via side set | |
| // psPin is controlled using set | |
| pio_gpio_init(pio, dataPin); | |
| pio_gpio_init(pio, clkPin); | |
| pio_gpio_init(pio, psPin); | |
| // configure pin directions | |
| pio_sm_set_pindirs_with_mask(pio, sm, (1 << clkPin) | (1 << psPin), (1 << dataPin) | (1 << clkPin) | (1 << psPin)); | |
| pio_sm_config c = NES_controller_interface_program_get_default_config(offset); | |
| // configure input, set and sideset pins. Only for set more than one pin can be configured at once. | |
| sm_config_set_in_pins(&c, dataPin); | |
| sm_config_set_set_pins(&c, psPin, 1); | |
| sm_config_set_sideset_pins(&c, clkPin); | |
| // configure sideset to use 1 bit, be mandatory and control pin values. | |
| // NOTE: that's already done by get_default_config(), not required here. | |
| //sm_config_set_sideset(&c, 1, false, false); | |
| // join FIFOs as we only need the RX FIFO | |
| sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); | |
| // set the clock divider to 35400 so the gamepad state is read every 20 ms (50 Hz) | |
| // each readout takes 20 instructions, leading to 80 cycles of the SM clock, i.e., | |
| // each clock cycle must last 250 us (4 kHz). To get a 4 kHz clock from the 141.6 MHz | |
| // system clock we must divide it by 35,400. | |
| sm_config_set_clkdiv_int_frac(&c, 35400, 0); | |
| // output shift register isn't used so we don't need to configure it. | |
| // configure the input shift register: Shift left, enable auto push, the register is | |
| // considered full after eight bits have been shifted in. | |
| sm_config_set_in_shift(&c, false, true, 8); | |
| // initialize SM | |
| pio_sm_init(pio, sm, offset, &c); | |
| // the PIO program isn't started here, that's done by gamepadStart(). | |
| } | |
| %} | |
| /***************************** | |
| * bt_nes_gamepad.gatt | |
| ***************************/ | |
| PRIMARY_SERVICE, GAP_SERVICE | |
| CHARACTERISTIC, GAP_DEVICE_NAME, READ, "NESblue Gamepad" | |
| PRIMARY_SERVICE, GATT_SERVICE | |
| CHARACTERISTIC, GATT_DATABASE_HASH, READ, | |
| PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE | |
| // report map maps the USB HID descriptor | |
| CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP, DYNAMIC | READ, | |
| CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, DYNAMIC | READ | NOTIFY | ENCRYPTION_KEY_SIZE_16, | |
| // reference descriptor containing report ID (1) and type (input == 1) | |
| REPORT_REFERENCE, READ, 1, 1 | |
| // we only have one input report, no output (to send data to the device) or | |
| // feature (to configure the device). | |
| // contains HID version (2B; here: 1.11), country code (1B) and device information (1B; see section 2.10 of BT HID service spec) | |
| // here: device is not remote wakeable (Bit 0) and normally connectable (Bit 1) | |
| CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION, READ, 01 11 00 02 | |
| // HID control point is used by the client to suspend our device (see section 2.11 of BT HID service spec) | |
| CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT, DYNAMIC | WRITE_WITHOUT_RESPONSE, | |
| /********************************** | |
| * server_common.h | |
| *****************************/ | |
| /** | |
| * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| #ifndef SERVER_COMMON_H_ | |
| #define SERVER_COMMON_H_ | |
| // these variables are defined in other source files | |
| extern int le_notification_enabled; | |
| extern uint8_t report[]; | |
| extern size_t const reportSize; | |
| extern uint8_t const profile_data[]; | |
| void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); | |
| uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); | |
| int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size); | |
| int gamepadBtInit(const uint8_t *usbHidDescriptor, size_t usbHidDescriptorSize, void (*suspCb)(int s)); | |
| void requestReportTransmission(); | |
| #endif | |
| /************************* | |
| * server_common.c | |
| ***********************/ | |
| /** | |
| * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| #include <stdio.h> | |
| #include "btstack.h" | |
| #include "bt_nes_gamepad.h" | |
| #include "server_common.h" | |
| // this is borrowed from Btstack's HIDS but adapted to our specific needs. | |
| // It contains a number of status variables. | |
| typedef struct{ | |
| // connection handle to remember which device we're currently connected to. | |
| uint16_t con_handle; | |
| const uint8_t * hid_descriptor;// -> USB report descriptor | |
| uint16_t hid_descriptor_size; | |
| // handles are identifiers Btstack assigns to the GATT entries. | |
| // this one maps Report IDs to specific reports to establish links between the USB HID descriptor | |
| // and the items in the GATT. | |
| uint16_t hid_report_map_handle; | |
| uint16_t hid_gamepad_input_value_handle;// we take the value from a global variable, it's not part of the device status. | |
| uint16_t hid_gamepad_input_client_configuration_handle;// -> used to enable notifications | |
| uint16_t hid_gamepad_input_client_configuration_value;// -> characteristic's current value (so we can identify changes) | |
| // the control point is used to suspend our device and wake it up. | |
| uint16_t hid_control_point_value_handle; | |
| uint8_t hid_control_point_suspend; | |
| } gamepad_device_t; | |
| // -> see Core Spec Supplement A, 1.3 (LE General Discoverable, no BR/EDR support) | |
| #define APP_AD_FLAGS 0x06 | |
| // advertisement data which is regularly broadcast so other devices can see our gamepad | |
| static uint8_t adv_data[] = { | |
| // Flags general discoverable | |
| 0x02, BLUETOOTH_DATA_TYPE_FLAGS, APP_AD_FLAGS, | |
| // Name | |
| 0x10, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'N', 'E', 'S', 'b', 'l', 'u', 'e', ' ', 'G', 'a', 'm', 'e', 'p', 'a', 'd', | |
| // Bluetooth HID service | |
| 0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x12, 0x18, | |
| // HID appearance (HID gamepad) | |
| 0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC4, 0x03, | |
| }; | |
| static const uint8_t adv_data_len = sizeof(adv_data); | |
| int le_notification_enabled; | |
| static gamepad_device_t gpstatus; | |
| static att_service_handler_t hid_service; | |
| static btstack_context_callback_registration_t notificationContext; | |
| // called when suspend status changes | |
| static void (*suspendCallback) (int suspended); | |
| void canSendNotification(void *context); | |
| void suspendDummy(int suspended); | |
| void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { | |
| UNUSED(size); | |
| UNUSED(channel); | |
| bd_addr_t local_addr; | |
| if (packet_type != HCI_EVENT_PACKET) return; | |
| uint8_t event_type = hci_event_packet_get_type(packet); | |
| // printf("HCI event: %02x\n", event_type); | |
| switch(event_type){ | |
| case BTSTACK_EVENT_STATE: | |
| if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return; | |
| gap_local_bd_addr(local_addr); | |
| printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); | |
| // setup advertisements | |
| uint16_t adv_int_min = 800; | |
| uint16_t adv_int_max = 800; | |
| 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); | |
| assert(adv_data_len <= 31); // BLE basic advertisements are limited to 31 bytes | |
| gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); | |
| gap_advertisements_enable(1); | |
| // and now we wait for things to come. | |
| break; | |
| case SM_EVENT_JUST_WORKS_REQUEST: | |
| printf("Just Works requested\n"); | |
| sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); | |
| break; | |
| case HCI_EVENT_DISCONNECTION_COMPLETE: | |
| le_notification_enabled = 0; | |
| gpstatus.con_handle = HCI_CON_HANDLE_INVALID; | |
| // Btstack automatically starts sending advertisements again. | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| int gamepadBtInit(const uint8_t *usbHidDescriptor, size_t usbHidDescriptorSize, void (*suspCb)(int s)) { | |
| // instead of looking at the header file generated from bt_nes_gamepad.h, we can query | |
| // the GATT server to get the handle values we need. | |
| uint16_t start_handle = 0; | |
| uint16_t end_handle = 0xffff; | |
| int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE, &start_handle, &end_handle); | |
| gpstatus.con_handle = HCI_CON_HANDLE_INVALID; | |
| gpstatus.hid_descriptor = usbHidDescriptor; | |
| gpstatus.hid_descriptor_size = usbHidDescriptorSize; | |
| if(suspCb == NULL) { | |
| suspendCallback = suspendDummy; | |
| } else { | |
| suspendCallback = suspCb; | |
| } | |
| // is there an HID service? If not, fail. | |
| btstack_assert(service_found != 0); | |
| UNUSED(service_found); | |
| // otherwise, find all the handles we need to identify which item of data is accessed. | |
| // The order in which we query the handles doesn't have to match the order they're defined. | |
| gpstatus.hid_report_map_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP); | |
| gpstatus.hid_control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT); | |
| gpstatus.hid_control_point_suspend = 0; | |
| gpstatus.hid_gamepad_input_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT); | |
| gpstatus.hid_gamepad_input_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT); | |
| gpstatus.hid_gamepad_input_client_configuration_value = 0; | |
| printf("report map handle: %04x\n", gpstatus.hid_report_map_handle); | |
| printf("input report handle: %04x\n", gpstatus.hid_gamepad_input_value_handle); | |
| printf("input report client configuration handle: %04x\n", gpstatus.hid_gamepad_input_client_configuration_handle); | |
| printf("control point handle: %04x\n", gpstatus.hid_control_point_value_handle); | |
| // now we have to configure our callback handler and register it with the ATT Server | |
| hid_service.start_handle = start_handle; | |
| hid_service.end_handle = end_handle; | |
| hid_service.read_callback = &att_read_callback; | |
| hid_service.write_callback = &att_write_callback; | |
| att_server_register_service_handler(&hid_service); | |
| notificationContext.callback = &canSendNotification; | |
| return 0; | |
| } | |
| // returns the number of bytes sent | |
| uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) { | |
| UNUSED(connection_handle); | |
| // using the ATT handle, we determine which characteristic the client wants to read - and what data we have to return. | |
| if (att_handle == gpstatus.hid_report_map_handle){ | |
| printf("Read report map\n"); | |
| // byte arrays are blobs | |
| return att_read_callback_handle_blob(gpstatus.hid_descriptor, gpstatus.hid_descriptor_size, offset, buffer, buffer_size); | |
| } | |
| if (att_handle == gpstatus.hid_control_point_value_handle){ | |
| // check whether buffer is != NULL and at least one byte in size | |
| if (buffer && (buffer_size >= 1u)){ | |
| buffer[0] = gpstatus.hid_control_point_suspend; | |
| } | |
| return 1; | |
| } | |
| if (att_handle == gpstatus.hid_gamepad_input_value_handle){ | |
| return att_read_callback_handle_blob((const uint8_t *)report, reportSize, offset, buffer, buffer_size); | |
| } | |
| if (att_handle == gpstatus.hid_gamepad_input_client_configuration_handle){ | |
| return att_read_callback_handle_little_endian_16(gpstatus.hid_gamepad_input_client_configuration_value, offset, buffer, buffer_size); | |
| } | |
| return 0; | |
| } | |
| int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { | |
| UNUSED(transaction_mode); | |
| UNUSED(offset); | |
| UNUSED(buffer_size); | |
| if (att_handle == gpstatus.hid_control_point_value_handle){ | |
| // check whether buffer has at least one byte | |
| if (buffer_size < 1u){ | |
| return ATT_ERROR_INVALID_OFFSET; | |
| } | |
| gpstatus.hid_control_point_suspend = buffer[0]; | |
| gpstatus.con_handle = connection_handle; | |
| // 0 means enter suspend, 1 means exit suspend (see BT HID spec, section 2.11.2). | |
| suspendCallback(gpstatus.hid_control_point_suspend == 0 ? 1 : 0); | |
| return 1; | |
| } | |
| if (att_handle == gpstatus.hid_gamepad_input_client_configuration_handle){ | |
| // writing to the client configuration can enable or disable notifications. | |
| le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; | |
| printf("Notifications, new value: %d\n", le_notification_enabled); | |
| // store connection handle and send the first notification if they were enabled. | |
| if (le_notification_enabled) { | |
| gpstatus.con_handle = connection_handle; | |
| //att_server_request_can_send_now_event(gpstatus.con_handle); | |
| requestReportTransmission(); | |
| } else { | |
| gpstatus.con_handle = HCI_CON_HANDLE_INVALID; | |
| } | |
| } | |
| return 0; | |
| } | |
| void canSendNotification(void *context) { | |
| // tell the ATT server we want to transmit a notification | |
| att_server_notify(gpstatus.con_handle, gpstatus.hid_gamepad_input_value_handle, report, reportSize); | |
| } | |
| inline void requestReportTransmission() { | |
| if(le_notification_enabled) { | |
| att_server_request_to_send_notification(¬ificationContext, gpstatus.con_handle); | |
| } | |
| } | |
| void suspendDummy(int suspended) { | |
| } | |
| /********************************* | |
| * CMakeLists.txt | |
| *************************/ | |
| #target_sources(bt_gamepad PRIVATE bt_gamepad.c nes_gamepad.c) | |
| # enable USB stdio | |
| pico_enable_stdio_usb(bt_gamepad 1) | |
| target_link_libraries(bt_gamepad | |
| pico_stdlib | |
| # for Bluetooth | |
| pico_btstack_ble | |
| pico_btstack_cyw43 | |
| pico_cyw43_arch_none | |
| # for PIO | |
| hardware_resets | |
| hardware_irq | |
| hardware_pio | |
| # hardware_adc | |
| ) | |
| target_include_directories(bt_gamepad PRIVATE | |
| ${CMAKE_CURRENT_LIST_DIR} # to add the folder "generated" created by Btstack into include path | |
| ) | |
| pico_btstack_make_gatt_header(bt_gamepad PRIVATE "${CMAKE_CURRENT_LIST_DIR}/bt_nes_gamepad.gatt") | |
| pico_add_extra_outputs(bt_gamepad) | |
| #example_auto_set_url(bt_gamepad) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment