数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 172|回复: 5

[Arduino] esp32c2(也即esp8684)大夏龙雀的吃灰板子做蓝牙手柄

[复制链接]
发表于 昨天 10:46 | 显示全部楼层 |阅读模式

爱科技、爱创意、爱折腾、爱极致,我们都是技术控

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 hackhack 于 2026-6-29 10:50 编辑

手上有很多手柄,但是没有一个“小巧的”,玩一些类似“推箱子”的益智游戏,用触摸屏不太舒服,用大手柄又太不方便。

本来想在网上买一个,某山的手柄之前很便宜,好像19块钱,现在30多了,算了,自己做一个吧。

用到的是大夏龙雀DX-WF25,2M flash。
最大的坑其实是26MHz的外置晶振。。。搞了我三天都没找到问题在哪儿。。。导致我一直搞不定蓝牙。官方文档的内容太少了,这种怎么能火起来。。。

3按键的测试版本源代码如下:


  1. #include <stdio.h>
  2. #include <string.h>
  3. #include "esp_log.h"
  4. #include "nvs_flash.h"
  5. #include "freertos/FreeRTOS.h"
  6. #include "freertos/task.h"
  7. #include "driver/gpio.h"

  8. /* NimBLE 核心头文件 */
  9. #include "nimble/nimble_port.h"
  10. #include "nimble/nimble_port_freertos.h"
  11. #include "host/ble_hs.h"
  12. #include "host/util/util.h"
  13. #include "services/gap/ble_svc_gap.h"
  14. #include "services/gatt/ble_svc_gatt.h"
  15. #include "store/config/ble_store_config.h"

  16. #define TAG "BLE_GAMEPAD"

  17. /* 按键 GPIO 分配 */
  18. #define GPIO_BTN_FORWARD  GPIO_NUM_2
  19. #define GPIO_BTN_BACKWARD GPIO_NUM_3
  20. #define GPIO_BTN_JUMP     GPIO_NUM_4

  21. /* 全局连接句柄 */
  22. static uint16_t current_conn_handle = BLE_HS_CONN_HANDLE_NONE;
  23. /* GATT 特征值句柄(用于发送通知) */
  24. static uint16_t hid_input_report_handle;

  25. static uint8_t protocol_mode = 0x01; // 0x01 = Report Mode

  26. /* 1. 标准 HID 游戏手柄报告描述符 */
  27. static const uint8_t hid_report_map[] = {
  28.     0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
  29.     0x09, 0x05,        // Usage (Game Pad)
  30.     0xa1, 0x01,        // Collection (Application)
  31.     0x85, 0x01,        //   Report ID (1)
  32.    
  33.     // 3个数字按键 (Bit 0: 前, Bit 1: 后, Bit 2: 跳)
  34.     0x05, 0x09,        //   Usage Page (Button)
  35.     0x19, 0x01,        //   Usage Minimum (0x01)
  36.     0x29, 0x03,        //   Usage Maximum (0x03)
  37.     0x15, 0x00,        //   Logical Minimum (0)
  38.     0x25, 0x01,        //   Logical Maximum (1)
  39.     0x75, 0x01,        //   Report Size (1)
  40.     0x95, 0x03,        //   Report Count (3)
  41.     0x81, 0x02,        //   Input (Data,Var,Abs)
  42.    
  43.     // 5位填充(对齐到1字节)
  44.     0x75, 0x01,        //   Report Size (1)
  45.     0x95, 0x05,        //   Report Count (5)
  46.     0x81, 0x03,        //   Input (Const,Var,Abs)
  47.    
  48.     // 虚拟出 X 和 Y 模拟轴(置中),防止部分系统因缺少非数字轴而拒绝枚举手柄
  49.     0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
  50.     0x09, 0x30,        //   Usage (X)
  51.     0x09, 0x31,        //   Usage (Y)
  52.     0x15, 0x81,        //   Logical Minimum (-127)
  53.     0x25, 0x7f,        //   Logical Maximum (127)
  54.     0x75, 0x08,        //   Report Size (8)
  55.     0x95, 0x02,        //   Report Count (2)
  56.     0x81, 0x02,        //   Input (Data,Var,Abs)
  57.    
  58.     0xc0               // End Collection
  59. };

  60. /* HID 基础信息 */
  61. static const uint8_t hid_info[4] = {
  62.     0x11, 0x01, // bcdHID (v1.11)
  63.     0x00,       // bCountryCode
  64.     0x03        // Flags: RemoteWake | NormallyConnectable
  65. };

  66. /* 报告引用描述符 (指向 Report ID 1, Type: Input) */
  67. static const uint8_t hid_report_reference[2] = {0x01, 0x01};

  68. /* PnP ID 信息 (伪装成标准设备以提高 Windows 兼容性) */
  69. static const uint8_t pnp_id[7] = {
  70.     0x02,       // Source: USB IF
  71.     0x5e, 0x04, // VID: 0x045E (Microsoft)
  72.     0x8e, 0x02, // PID: 0x028E (Xbox 360 Controller)
  73.     0x00, 0x01  // Version: 1.0.0
  74. };

  75. /* GATT 回调声明 */
  76. static int gatt_svr_chr_access_dis(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
  77. static int gatt_svr_chr_access_hid(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
  78. static int ble_app_gap_event(struct ble_gap_event *event, void *arg);
  79. static void ble_app_advertise(uint8_t addr_type);

  80. /* 2. GATT 服务及特征值定义 */
  81. static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
  82.     {
  83.         /* 设备信息服务 (DIS) */
  84.         .type = BLE_GATT_SVC_TYPE_PRIMARY,
  85.         .uuid = BLE_UUID16_DECLARE(0x180A),
  86.         .characteristics = (struct ble_gatt_chr_def[]) { {
  87.             .uuid = BLE_UUID16_DECLARE(0x2A50), // PnP ID
  88.             .access_cb = gatt_svr_chr_access_dis,
  89.             .flags = BLE_GATT_CHR_F_READ,
  90.         }, {
  91.             0,
  92.         } }
  93.     },
  94.     {
  95.         /* 人机接口设备服务 (HID) */
  96.         .type = BLE_GATT_SVC_TYPE_PRIMARY,
  97.         .uuid = BLE_UUID16_DECLARE(0x1812),
  98.         .characteristics = (struct ble_gatt_chr_def[]) { {
  99.             .uuid = BLE_UUID16_DECLARE(0x2A4A), // HID Information
  100.             .access_cb = gatt_svr_chr_access_hid,
  101.             .flags = BLE_GATT_CHR_F_READ,
  102.         }, {
  103.             .uuid = BLE_UUID16_DECLARE(0x2A4B), // Report Map
  104.             .access_cb = gatt_svr_chr_access_hid,
  105.             .flags = BLE_GATT_CHR_F_READ,
  106.         }, {
  107.             .uuid = BLE_UUID16_DECLARE(0x2A4C), // HID Control Point
  108.             .access_cb = gatt_svr_chr_access_hid,
  109.             .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
  110.         }, {
  111.             .uuid = BLE_UUID16_DECLARE(0x2A4D), // Report (Input)
  112.             .access_cb = gatt_svr_chr_access_hid,
  113.             .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
  114.             .val_handle = &hid_input_report_handle,
  115.             .descriptors = (struct ble_gatt_dsc_def[]) { {
  116.                 .uuid = BLE_UUID16_DECLARE(0x2908), // Report Reference
  117.                 .access_cb = gatt_svr_chr_access_hid,
  118.                 /* 【修复 1】将 .flags 变更为 .att_flags,并将宏改为 BLE_ATT_F_READ */
  119.                 .att_flags = BLE_ATT_F_READ,
  120.             }, {
  121.                 0,
  122.             } }
  123.         }, {
  124.             .uuid = BLE_UUID16_DECLARE(0x2A4E), // Protocol Mode
  125.             .access_cb = gatt_svr_chr_access_hid,
  126.             .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP,
  127.         }, {
  128.             0,
  129.         } }
  130.     },
  131.     { 0 }
  132. };

  133. static int gatt_svr_chr_access_dis(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
  134.     if (ble_uuid_u16(ctxt->chr->uuid) == 0x2A50) {
  135.         return os_mbuf_append(ctxt->om, pnp_id, sizeof(pnp_id)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  136.     }
  137.     return BLE_ATT_ERR_UNLIKELY;
  138. }

  139. static int gatt_svr_chr_access_hid(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
  140.     uint16_t uuid = ble_uuid_u16(ctxt->chr->uuid);
  141.     if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) {
  142.         if (uuid == 0x2A4A) return os_mbuf_append(ctxt->om, hid_info, sizeof(hid_info)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  143.         if (uuid == 0x2A4B) return os_mbuf_append(ctxt->om, hid_report_map, sizeof(hid_report_map)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  144.         if (uuid == 0x2908) return os_mbuf_append(ctxt->om, hid_report_reference, sizeof(hid_report_reference)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  145.         if (uuid == 0x2A4E) return os_mbuf_append(ctxt->om, &protocol_mode, sizeof(protocol_mode)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  146.         if (uuid == 0x2A4D) {
  147.             uint8_t empty_report[3] = {0, 0, 0};
  148.             return os_mbuf_append(ctxt->om, empty_report, sizeof(empty_report)) == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
  149.         }
  150.     } else if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
  151.         if (uuid == 0x2A4C) return 0;
  152.         if (uuid == 0x2A4E && ctxt->om->om_len == 1) {
  153.             protocol_mode = ctxt->om->om_data[0];
  154.             return 0;
  155.         }
  156.     }
  157.     return BLE_ATT_ERR_UNLIKELY;
  158. }

  159. /* 3. 蓝牙 GAP 状态机广播控制 */
  160. static void ble_app_advertise(uint8_t addr_type) {
  161.     struct ble_gap_adv_params adv_params;
  162.     struct ble_hs_adv_fields fields;
  163.     int rc;

  164.     memset(&fields, 0, sizeof(fields));
  165.     fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
  166.    
  167.     fields.appearance = 0x03C4; // Gamepad
  168.     fields.appearance_is_present = 1;

  169.     const char *name = "ESP32C2-Gamepad";
  170.     fields.name = (uint8_t *)name;
  171.     fields.name_len = strlen(name);
  172.     fields.name_is_complete = 1;

  173.     ble_uuid16_t hid_uuid = BLE_UUID16_INIT(0x1812);
  174.     fields.uuids16 = &hid_uuid;
  175.     fields.num_uuids16 = 1;
  176.     fields.uuids16_is_complete = 1;

  177.     rc = ble_gap_adv_set_fields(&fields);
  178.     if (rc != 0) {
  179.         ESP_LOGE(TAG, "设置广播数据失败: %d", rc);
  180.         return;
  181.     }

  182.     memset(&adv_params, 0, sizeof(adv_params));
  183.     adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
  184.     adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;

  185.     rc = ble_gap_adv_start(addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_app_gap_event, NULL);
  186.     if (rc != 0) {
  187.         ESP_LOGE(TAG, "开启广播失败: %d", rc);
  188.     } else {
  189.         ESP_LOGI(TAG, "手柄蓝牙广播已开启...");
  190.     }
  191. }

  192. static int ble_app_gap_event(struct ble_gap_event *event, void *arg) {
  193.     struct ble_gap_conn_desc desc;
  194.     switch (event->type) {
  195.         case BLE_GAP_EVENT_CONNECT:
  196.             ESP_LOGI(TAG, "设备已连接, 状态=%d", event->connect.status);
  197.             if (event->connect.status == 0) {
  198.                 current_conn_handle = event->connect.conn_handle;
  199.                 ble_gap_security_initiate(current_conn_handle);
  200.             } else {
  201.                 uint8_t addr_type;
  202.                 ble_hs_id_infer_auto(0, &addr_type);
  203.                 ble_app_advertise(addr_type);
  204.             }
  205.             return 0;

  206.         case BLE_GAP_EVENT_DISCONNECT:
  207.             ESP_LOGI(TAG, "断开连接,原因=%d", event->disconnect.reason);
  208.             current_conn_handle = BLE_HS_CONN_HANDLE_NONE;
  209.             uint8_t addr_type;
  210.             ble_hs_id_infer_auto(0, &addr_type);
  211.             ble_app_advertise(addr_type);
  212.             return 0;

  213.         case BLE_GAP_EVENT_REPEAT_PAIRING:
  214.             if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) {
  215.                 ble_store_util_delete_peer(&desc.peer_id_addr);
  216.             }
  217.             return BLE_GAP_REPEAT_PAIRING_RETRY;

  218.         case BLE_GAP_EVENT_ENC_CHANGE:
  219.             ESP_LOGI(TAG, "加密状态改变: %d", event->enc_change.status);
  220.             return 0;
  221.     }
  222.     return 0;
  223. }

  224. static void ble_app_on_sync(void) {
  225.     uint8_t addr_type;
  226.     ble_hs_id_infer_auto(0, &addr_type);
  227.     ble_app_advertise(addr_type);
  228. }

  229. /* 4. 手柄 HID 报告发送函数 */
  230. void send_gamepad_report(uint8_t buttons) {
  231.     if (current_conn_handle == BLE_HS_CONN_HANDLE_NONE) {
  232.         return;
  233.     }

  234.     uint8_t report_data[3] = { buttons, 0, 0 };
  235.     struct os_mbuf *om = ble_hs_mbuf_from_flat(report_data, sizeof(report_data));
  236.    
  237.     if (om != NULL) {
  238.         int rc = ble_gattc_notify_custom(current_conn_handle, hid_input_report_handle, om);
  239.         if (rc != 0) {
  240.             ESP_LOGE(TAG, "HID 报告发送失败, 错误码=%d", rc);
  241.         }
  242.     }
  243. }

  244. /* 5. 按键扫描任务 */
  245. void button_scan_task(void *pvParameters) {
  246.     gpio_config_t io_conf = {
  247.         .pin_bit_mask = (1ULL << GPIO_BTN_FORWARD) | (1ULL << GPIO_BTN_BACKWARD) | (1ULL << GPIO_BTN_JUMP),
  248.         .mode = GPIO_MODE_INPUT,
  249.         .pull_up_en = GPIO_PULLUP_ENABLE,
  250.         .pull_down_en = GPIO_PULLDOWN_DISABLE,
  251.         .intr_type = GPIO_INTR_DISABLE
  252.     };
  253.     gpio_config(&io_conf);

  254.     uint8_t last_buttons_state = 0;

  255.     while (1) {
  256.         uint8_t current_buttons = 0;

  257.         if (gpio_get_level(GPIO_BTN_FORWARD) == 0)  current_buttons |= (1 << 0);
  258.         if (gpio_get_level(GPIO_BTN_BACKWARD) == 0) current_buttons |= (1 << 1);
  259.         if (gpio_get_level(GPIO_BTN_JUMP) == 0)     current_buttons |= (1 << 2);

  260.         if (current_buttons != last_buttons_state) {
  261.             last_buttons_state = current_buttons;
  262.             send_gamepad_report(current_buttons);
  263.             ESP_LOGI(TAG, "按键触发变换: 0x%02X", current_buttons);
  264.         }

  265.         vTaskDelay(pdMS_TO_TICKS(15));
  266.     }
  267. }

  268. void nimble_host_task(void *param) {
  269.     ESP_LOGI(TAG, "NimBLE 主任务已启动");
  270.     nimble_port_run();
  271.     nimble_port_freertos_deinit();
  272. }

  273. /* 6. 程序主入口 */
  274. void app_main(void) {
  275.     esp_err_t ret = nvs_flash_init();
  276.     if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
  277.         ESP_ERROR_CHECK(nvs_flash_erase());
  278.         ret = nvs_flash_init();
  279.     }
  280.     ESP_ERROR_CHECK(ret);

  281.     ESP_ERROR_CHECK(nimble_port_init());
  282.    
  283.     ble_svc_gap_init();
  284.     ble_svc_gatt_init();
  285.    
  286.     int rc = ble_gatts_count_cfg(gatt_svr_svcs);
  287.     if (rc != 0) return;
  288.     rc = ble_gatts_add_svcs(gatt_svr_svcs);
  289.     if (rc != 0) return;

  290.     ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
  291.     ble_hs_cfg.sm_bonding = 1;
  292.     ble_hs_cfg.sm_mitm = 0;
  293.     ble_hs_cfg.sm_sc = 1;
  294.     ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
  295.     ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;

  296.     /* 【修复 2】移除已被废弃的 ble_store_config_init() 包装函数 */
  297.     /* 直接将读写回调指针赋给全局配对存储配置 */
  298.     ble_hs_cfg.store_read_cb = ble_store_config_read;
  299.     ble_hs_cfg.store_write_cb = ble_store_config_write;

  300.     ble_svc_gap_device_name_set("ESP32C2-Gamepad");
  301.     ble_hs_cfg.sync_cb = ble_app_on_sync;

  302.     nimble_port_freertos_init(nimble_host_task);

  303.     xTaskCreate(button_scan_task, "button_scan_task", 2048, NULL, 5, NULL);
  304. }
复制代码



sdkconfig.defaults内容如下:

  1. # 芯片目标
  2. CONFIG_IDF_TARGET="esp32c2"

  3. # 26MHz 外置晶振关键配置
  4. CONFIG_XTAL_FREQ_26=y
  5. CONFIG_XTAL_FREQ=26
  6. CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_120=y

  7. # 闪存大小配置 (2MB)
  8. CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
  9. CONFIG_PARTITION_TABLE_CUSTOM=y
  10. CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"

  11. # 编译器优化:优化大小 (-Os) 极致省闪存
  12. CONFIG_COMPILER_OPTIMIZATION_SIZE=y

  13. # 彻底关闭 Wi-Fi,根除共存丢包,极大释放内存空间
  14. CONFIG_ESP_WIFI_ENABLED=n

  15. # 蓝牙底层配置:启用 NimBLE,禁用臃肿的 Bluedroid
  16. CONFIG_BT_ENABLED=y
  17. CONFIG_BT_BLUEDROID_ENABLED=n
  18. CONFIG_BT_NIMBLE_ENABLED=y

  19. # NimBLE 内存极致裁剪
  20. CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
  21. CONFIG_BT_NIMBLE_MAX_BONDS=3
  22. CONFIG_BT_NIMBLE_MAX_CCCDS=8
  23. CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12

复制代码



注意日志需要的串口波特率为74880





打赏

参与人数 1家元 +6 收起 理由
o535913 + 6 優秀文章

查看全部打赏

 楼主| 发表于 昨天 10:55 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

发表于 昨天 12:20 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 12:23 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

发表于 昨天 14:04 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 15:37 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|数码之家-技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2026-6-30 14:18 , Processed in 0.093600 second(s), 11 queries , Gzip On, Redis On.

Powered by Discuz!

© MyDigit.Net Since 2006

快速回复 返回顶部 返回列表