|
|
爱科技、爱创意、爱折腾、爱极致,我们都是技术控
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 hackhack 于 2026-6-29 10:50 编辑
手上有很多手柄,但是没有一个“小巧的”,玩一些类似“推箱子”的益智游戏,用触摸屏不太舒服,用大手柄又太不方便。
本来想在网上买一个,某山的手柄之前很便宜,好像19块钱,现在30多了,算了,自己做一个吧。
用到的是大夏龙雀DX-WF25,2M flash。
最大的坑其实是26MHz的外置晶振。。。搞了我三天都没找到问题在哪儿。。。导致我一直搞不定蓝牙。官方文档的内容太少了,这种怎么能火起来。。。
3按键的测试版本源代码如下:
- #include <stdio.h>
- #include <string.h>
- #include "esp_log.h"
- #include "nvs_flash.h"
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "driver/gpio.h"
- /* NimBLE 核心头文件 */
- #include "nimble/nimble_port.h"
- #include "nimble/nimble_port_freertos.h"
- #include "host/ble_hs.h"
- #include "host/util/util.h"
- #include "services/gap/ble_svc_gap.h"
- #include "services/gatt/ble_svc_gatt.h"
- #include "store/config/ble_store_config.h"
- #define TAG "BLE_GAMEPAD"
- /* 按键 GPIO 分配 */
- #define GPIO_BTN_FORWARD GPIO_NUM_2
- #define GPIO_BTN_BACKWARD GPIO_NUM_3
- #define GPIO_BTN_JUMP GPIO_NUM_4
- /* 全局连接句柄 */
- static uint16_t current_conn_handle = BLE_HS_CONN_HANDLE_NONE;
- /* GATT 特征值句柄(用于发送通知) */
- static uint16_t hid_input_report_handle;
- static uint8_t protocol_mode = 0x01; // 0x01 = Report Mode
- /* 1. 标准 HID 游戏手柄报告描述符 */
- static const uint8_t hid_report_map[] = {
- 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
- 0x09, 0x05, // Usage (Game Pad)
- 0xa1, 0x01, // Collection (Application)
- 0x85, 0x01, // Report ID (1)
-
- // 3个数字按键 (Bit 0: 前, Bit 1: 后, Bit 2: 跳)
- 0x05, 0x09, // Usage Page (Button)
- 0x19, 0x01, // Usage Minimum (0x01)
- 0x29, 0x03, // Usage Maximum (0x03)
- 0x15, 0x00, // Logical Minimum (0)
- 0x25, 0x01, // Logical Maximum (1)
- 0x75, 0x01, // Report Size (1)
- 0x95, 0x03, // Report Count (3)
- 0x81, 0x02, // Input (Data,Var,Abs)
-
- // 5位填充(对齐到1字节)
- 0x75, 0x01, // Report Size (1)
- 0x95, 0x05, // Report Count (5)
- 0x81, 0x03, // Input (Const,Var,Abs)
-
- // 虚拟出 X 和 Y 模拟轴(置中),防止部分系统因缺少非数字轴而拒绝枚举手柄
- 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
- 0x09, 0x30, // Usage (X)
- 0x09, 0x31, // Usage (Y)
- 0x15, 0x81, // Logical Minimum (-127)
- 0x25, 0x7f, // Logical Maximum (127)
- 0x75, 0x08, // Report Size (8)
- 0x95, 0x02, // Report Count (2)
- 0x81, 0x02, // Input (Data,Var,Abs)
-
- 0xc0 // End Collection
- };
- /* HID 基础信息 */
- static const uint8_t hid_info[4] = {
- 0x11, 0x01, // bcdHID (v1.11)
- 0x00, // bCountryCode
- 0x03 // Flags: RemoteWake | NormallyConnectable
- };
- /* 报告引用描述符 (指向 Report ID 1, Type: Input) */
- static const uint8_t hid_report_reference[2] = {0x01, 0x01};
- /* PnP ID 信息 (伪装成标准设备以提高 Windows 兼容性) */
- static const uint8_t pnp_id[7] = {
- 0x02, // Source: USB IF
- 0x5e, 0x04, // VID: 0x045E (Microsoft)
- 0x8e, 0x02, // PID: 0x028E (Xbox 360 Controller)
- 0x00, 0x01 // Version: 1.0.0
- };
- /* GATT 回调声明 */
- static int gatt_svr_chr_access_dis(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
- static int gatt_svr_chr_access_hid(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
- static int ble_app_gap_event(struct ble_gap_event *event, void *arg);
- static void ble_app_advertise(uint8_t addr_type);
- /* 2. GATT 服务及特征值定义 */
- static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
- {
- /* 设备信息服务 (DIS) */
- .type = BLE_GATT_SVC_TYPE_PRIMARY,
- .uuid = BLE_UUID16_DECLARE(0x180A),
- .characteristics = (struct ble_gatt_chr_def[]) { {
- .uuid = BLE_UUID16_DECLARE(0x2A50), // PnP ID
- .access_cb = gatt_svr_chr_access_dis,
- .flags = BLE_GATT_CHR_F_READ,
- }, {
- 0,
- } }
- },
- {
- /* 人机接口设备服务 (HID) */
- .type = BLE_GATT_SVC_TYPE_PRIMARY,
- .uuid = BLE_UUID16_DECLARE(0x1812),
- .characteristics = (struct ble_gatt_chr_def[]) { {
- .uuid = BLE_UUID16_DECLARE(0x2A4A), // HID Information
- .access_cb = gatt_svr_chr_access_hid,
- .flags = BLE_GATT_CHR_F_READ,
- }, {
- .uuid = BLE_UUID16_DECLARE(0x2A4B), // Report Map
- .access_cb = gatt_svr_chr_access_hid,
- .flags = BLE_GATT_CHR_F_READ,
- }, {
- .uuid = BLE_UUID16_DECLARE(0x2A4C), // HID Control Point
- .access_cb = gatt_svr_chr_access_hid,
- .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
- }, {
- .uuid = BLE_UUID16_DECLARE(0x2A4D), // Report (Input)
- .access_cb = gatt_svr_chr_access_hid,
- .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
- .val_handle = &hid_input_report_handle,
- .descriptors = (struct ble_gatt_dsc_def[]) { {
- .uuid = BLE_UUID16_DECLARE(0x2908), // Report Reference
- .access_cb = gatt_svr_chr_access_hid,
- /* 【修复 1】将 .flags 变更为 .att_flags,并将宏改为 BLE_ATT_F_READ */
- .att_flags = BLE_ATT_F_READ,
- }, {
- 0,
- } }
- }, {
- .uuid = BLE_UUID16_DECLARE(0x2A4E), // Protocol Mode
- .access_cb = gatt_svr_chr_access_hid,
- .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP,
- }, {
- 0,
- } }
- },
- { 0 }
- };
- static int gatt_svr_chr_access_dis(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
- if (ble_uuid_u16(ctxt->chr->uuid) == 0x2A50) {
- return os_mbuf_append(ctxt->om, pnp_id, sizeof(pnp_id)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- }
- return BLE_ATT_ERR_UNLIKELY;
- }
- static int gatt_svr_chr_access_hid(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
- uint16_t uuid = ble_uuid_u16(ctxt->chr->uuid);
- if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) {
- if (uuid == 0x2A4A) return os_mbuf_append(ctxt->om, hid_info, sizeof(hid_info)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- if (uuid == 0x2A4B) return os_mbuf_append(ctxt->om, hid_report_map, sizeof(hid_report_map)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- if (uuid == 0x2908) return os_mbuf_append(ctxt->om, hid_report_reference, sizeof(hid_report_reference)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- if (uuid == 0x2A4E) return os_mbuf_append(ctxt->om, &protocol_mode, sizeof(protocol_mode)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- if (uuid == 0x2A4D) {
- uint8_t empty_report[3] = {0, 0, 0};
- return os_mbuf_append(ctxt->om, empty_report, sizeof(empty_report)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
- }
- } else if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
- if (uuid == 0x2A4C) return 0;
- if (uuid == 0x2A4E && ctxt->om->om_len == 1) {
- protocol_mode = ctxt->om->om_data[0];
- return 0;
- }
- }
- return BLE_ATT_ERR_UNLIKELY;
- }
- /* 3. 蓝牙 GAP 状态机广播控制 */
- static void ble_app_advertise(uint8_t addr_type) {
- struct ble_gap_adv_params adv_params;
- struct ble_hs_adv_fields fields;
- int rc;
- memset(&fields, 0, sizeof(fields));
- fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
-
- fields.appearance = 0x03C4; // Gamepad
- fields.appearance_is_present = 1;
- const char *name = "ESP32C2-Gamepad";
- fields.name = (uint8_t *)name;
- fields.name_len = strlen(name);
- fields.name_is_complete = 1;
- ble_uuid16_t hid_uuid = BLE_UUID16_INIT(0x1812);
- fields.uuids16 = &hid_uuid;
- fields.num_uuids16 = 1;
- fields.uuids16_is_complete = 1;
- rc = ble_gap_adv_set_fields(&fields);
- if (rc != 0) {
- ESP_LOGE(TAG, "设置广播数据失败: %d", rc);
- return;
- }
- memset(&adv_params, 0, sizeof(adv_params));
- adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
- adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
- rc = ble_gap_adv_start(addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_app_gap_event, NULL);
- if (rc != 0) {
- ESP_LOGE(TAG, "开启广播失败: %d", rc);
- } else {
- ESP_LOGI(TAG, "手柄蓝牙广播已开启...");
- }
- }
- static int ble_app_gap_event(struct ble_gap_event *event, void *arg) {
- struct ble_gap_conn_desc desc;
- switch (event->type) {
- case BLE_GAP_EVENT_CONNECT:
- ESP_LOGI(TAG, "设备已连接, 状态=%d", event->connect.status);
- if (event->connect.status == 0) {
- current_conn_handle = event->connect.conn_handle;
- ble_gap_security_initiate(current_conn_handle);
- } else {
- uint8_t addr_type;
- ble_hs_id_infer_auto(0, &addr_type);
- ble_app_advertise(addr_type);
- }
- return 0;
- case BLE_GAP_EVENT_DISCONNECT:
- ESP_LOGI(TAG, "断开连接,原因=%d", event->disconnect.reason);
- current_conn_handle = BLE_HS_CONN_HANDLE_NONE;
- uint8_t addr_type;
- ble_hs_id_infer_auto(0, &addr_type);
- ble_app_advertise(addr_type);
- return 0;
- case BLE_GAP_EVENT_REPEAT_PAIRING:
- if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) {
- ble_store_util_delete_peer(&desc.peer_id_addr);
- }
- return BLE_GAP_REPEAT_PAIRING_RETRY;
- case BLE_GAP_EVENT_ENC_CHANGE:
- ESP_LOGI(TAG, "加密状态改变: %d", event->enc_change.status);
- return 0;
- }
- return 0;
- }
- static void ble_app_on_sync(void) {
- uint8_t addr_type;
- ble_hs_id_infer_auto(0, &addr_type);
- ble_app_advertise(addr_type);
- }
- /* 4. 手柄 HID 报告发送函数 */
- void send_gamepad_report(uint8_t buttons) {
- if (current_conn_handle == BLE_HS_CONN_HANDLE_NONE) {
- return;
- }
- uint8_t report_data[3] = { buttons, 0, 0 };
- struct os_mbuf *om = ble_hs_mbuf_from_flat(report_data, sizeof(report_data));
-
- if (om != NULL) {
- int rc = ble_gattc_notify_custom(current_conn_handle, hid_input_report_handle, om);
- if (rc != 0) {
- ESP_LOGE(TAG, "HID 报告发送失败, 错误码=%d", rc);
- }
- }
- }
- /* 5. 按键扫描任务 */
- void button_scan_task(void *pvParameters) {
- gpio_config_t io_conf = {
- .pin_bit_mask = (1ULL << GPIO_BTN_FORWARD) | (1ULL << GPIO_BTN_BACKWARD) | (1ULL << GPIO_BTN_JUMP),
- .mode = GPIO_MODE_INPUT,
- .pull_up_en = GPIO_PULLUP_ENABLE,
- .pull_down_en = GPIO_PULLDOWN_DISABLE,
- .intr_type = GPIO_INTR_DISABLE
- };
- gpio_config(&io_conf);
- uint8_t last_buttons_state = 0;
- while (1) {
- uint8_t current_buttons = 0;
- if (gpio_get_level(GPIO_BTN_FORWARD) == 0) current_buttons |= (1 << 0);
- if (gpio_get_level(GPIO_BTN_BACKWARD) == 0) current_buttons |= (1 << 1);
- if (gpio_get_level(GPIO_BTN_JUMP) == 0) current_buttons |= (1 << 2);
- if (current_buttons != last_buttons_state) {
- last_buttons_state = current_buttons;
- send_gamepad_report(current_buttons);
- ESP_LOGI(TAG, "按键触发变换: 0x%02X", current_buttons);
- }
- vTaskDelay(pdMS_TO_TICKS(15));
- }
- }
- void nimble_host_task(void *param) {
- ESP_LOGI(TAG, "NimBLE 主任务已启动");
- nimble_port_run();
- nimble_port_freertos_deinit();
- }
- /* 6. 程序主入口 */
- void app_main(void) {
- esp_err_t ret = nvs_flash_init();
- if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ret = nvs_flash_init();
- }
- ESP_ERROR_CHECK(ret);
- ESP_ERROR_CHECK(nimble_port_init());
-
- ble_svc_gap_init();
- ble_svc_gatt_init();
-
- int rc = ble_gatts_count_cfg(gatt_svr_svcs);
- if (rc != 0) return;
- rc = ble_gatts_add_svcs(gatt_svr_svcs);
- if (rc != 0) return;
- ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
- ble_hs_cfg.sm_bonding = 1;
- ble_hs_cfg.sm_mitm = 0;
- ble_hs_cfg.sm_sc = 1;
- ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
- ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
- /* 【修复 2】移除已被废弃的 ble_store_config_init() 包装函数 */
- /* 直接将读写回调指针赋给全局配对存储配置 */
- ble_hs_cfg.store_read_cb = ble_store_config_read;
- ble_hs_cfg.store_write_cb = ble_store_config_write;
- ble_svc_gap_device_name_set("ESP32C2-Gamepad");
- ble_hs_cfg.sync_cb = ble_app_on_sync;
- nimble_port_freertos_init(nimble_host_task);
- xTaskCreate(button_scan_task, "button_scan_task", 2048, NULL, 5, NULL);
- }
复制代码
sdkconfig.defaults内容如下:
- # 芯片目标
- CONFIG_IDF_TARGET="esp32c2"
- # 26MHz 外置晶振关键配置
- CONFIG_XTAL_FREQ_26=y
- CONFIG_XTAL_FREQ=26
- CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_120=y
- # 闪存大小配置 (2MB)
- CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
- CONFIG_PARTITION_TABLE_CUSTOM=y
- CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
- # 编译器优化:优化大小 (-Os) 极致省闪存
- CONFIG_COMPILER_OPTIMIZATION_SIZE=y
- # 彻底关闭 Wi-Fi,根除共存丢包,极大释放内存空间
- CONFIG_ESP_WIFI_ENABLED=n
- # 蓝牙底层配置:启用 NimBLE,禁用臃肿的 Bluedroid
- CONFIG_BT_ENABLED=y
- CONFIG_BT_BLUEDROID_ENABLED=n
- CONFIG_BT_NIMBLE_ENABLED=y
- # NimBLE 内存极致裁剪
- CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
- CONFIG_BT_NIMBLE_MAX_BONDS=3
- CONFIG_BT_NIMBLE_MAX_CCCDS=8
- CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12
复制代码
注意日志需要的串口波特率为74880
|
打赏
-
查看全部打赏
|