2.3.5. 蓝牙键盘

  • 本案例主要用于标准双模蓝牙键盘的实现,设备开机之后进入配对状态,用于可以与能搜索到 蓝牙 3.0、蓝牙 4.0 两种设备进行连接,连接成功之后,另一个会消失。SDK 默认支持 4 个设备连接 键盘(同时只能连接一个),通过按键进行切换设备。

  • 支持板级:br23、bd19 支持芯片:AC6351D、AC6321A

  • 对于bd19的键盘方案:新增加了按键扫描模块 AD15N,除了处理按键扫描有点区别外,其他部分 实现方式跟 AC6351D 实现方式是一致的,需要在批处理文件中添加.bin文件用于编译 tools/download/data_trans/download.bat。

  • 用记事本的方式打开 tools/download/data_trans/download.bat文件,并在下列位置添加 ../../ex_mcu.bin 这行代码

    ../../isd_download.exe ../../isd_config.ini -tonorflash -dev bd19 -boot 0x2000 -div8 -wait 300 -uboot ../../uboot.boot -app ../../app.bin ../../cfg_tool.bin -res ../../p11_code.bin `../../ex_mcu.bin` -uboot_compress
    

2.3.5.1. 蓝牙键盘case使用

  • 因为下列键盘使用的br23键盘举例,首先关闭之前bd19的工程,打开/fw-AC63_BT_SDK-ac63_bt_sdk_release_vx.x.x/apps/hid/board/br23下的AC635N_hid.cbp即可打开工程

  • 使用快捷键Alt + g 打开app_config.h,配置CONFIG_APP_KEYFOB使能

//app case 选择,只选1,要配置对应的board_config.h
#define CONFIG_APP_STANDARD_KEYBOARD        1//标准HID键盘,board_ac6351d
  • 使用快捷键Alt + g 打开board_config.h,板级选择CONFIG_BOARD_AC6328A_KEYFOB

// 板级配置选择
#define #define CONFIG_BOARD_AC6351D_KEYBOARD//标准 HID 键盘,board_ac6351d
  • 按照 :HID DEMO说明 编译下载代码、接线(接线参考下列)、复位设备,再使用手机搜索设备进行连接。

    MATRIX KEY: key接线需要打开board_ac6351d_keyboard.c查看MATRIX key接线,分别有行和列。

    /************************** MATRIX KEY ****************************/
    #if TCFG_MATRIX_KEY_ENABLE
    
    #if TCFG_MATRIX_KEY_CORE == 1
    static u32 key_row[] = {IO_PORTB_09, IO_PORTB_07, IO_PORTB_11, IO_PORTC_07,    IO_PORTB_10, IO_PORTC_06, IO_PORTB_06, IO_PORTB_08 };
    
    static u32 key_col[] = {IO_PORTA_04, IO_PORTA_00, IO_PORTA_02, IO_PORTA_01,    IO_PORTA_03, IO_PORTA_05, IO_PORTA_08, IO_PORTA_06, \
             IO_PORTA_07, IO_PORTA_12, IO_PORTC_00, IO_PORTA_15,    IO_PORTC_01, IO_PORTB_05, IO_PORTA_13, IO_PORTA_14, \
             IO_PORTA_09, IO_PORTA_11, IO_PORTA_10,
           };
    

    触摸板 KEY:

    //*********************************************************************************//
    //                                 触摸板 配置                                      //
    //*********************************************************************************//
    #define TCFG_TOUCHPAD_ENABLE                DISABLE_THIS_MOUDLE
    
  • 开发者在具有键盘样机并在烧录代码后可以操作FN + 1 的组合按键(按下FN 不松开,在按下 1 即可)打开蓝牙功能,再使用电脑或者手机连接键盘使用。

2.3.5.2. 按键唤醒说明

  • 键盘进入低功耗之后需要通过按键唤醒cpu,635N支持8个普通IO、LVD唤醒、LDOIN唤醒(board_ac6351d_keyboard.c)

    • 普通IO唤醒:

      struct port_wakeup port0 = {
          .pullup_down_enable = ENABLE,                            //配置I/O 内部上下拉是否使能
          .edge               = FALLING_EDGE,                      //唤醒方式选择,可选:上升沿\下降沿
          .attribute          = BLUETOOTH_RESUME,                  //保留参数
          .iomap              = IO_PORTB_06,                       //唤醒口选择
          .filter_enable      = ENABLE,
      };
      const struct wakeup_param wk_param = {
          .port[0] = &port0,
          ...
          ...
          .port[7] = &port7,
          .sub = &sub_wkup,
          .charge = &charge_wkup,
      };
      
    • LVD唤醒,LDOIN唤醒为充电唤醒:

      • lvd_extern_wakeup_enable(); //要根据封装来选择是否可以使用LVD唤醒, 6531C封装LVD是PB4

2.3.5.3. 键值的配置说明

  • app_standard_keyboard.c文件中定义了键盘的键值表matrix_key_table和 fn键重映射键值表fn_remap_event还要其他按键事件other_key_map

    • matrix_key_table 定义的是标准Keyboard的键值,如RCTRL、LCTRL、A、B等…, 用户根据方案选择键芯来修改键盘键值表,对应的键值功能定义在`apps/common/usb/host/usb_hid_keys.h`

    • fn_remap_event分为两种,一种用于系统控制,如音量加键、搜索查找等,另一种为用于客户自定义的功能,如蓝牙切换等, is_user_key为0表示按键为系统控制用,键值可以在COUSTOM_CONTROL页里找。

    • Is_user_key为1表示为用于自动义按键,跟标准HID无关,相关按键的处理再user_key_deal里处理。

2.3.5.4. 主要代码说明

APP注册运行:

  • 按照下述代码进行APP注册,执行配置好的app。之后进入APP_state_machine,根据状态机的不同状态执行不同的分支,第一次执行时进入APP_STA_CREATE分支,执行对应的app_start()。开始执行app_start()在该函数内进行时钟初始化,进行蓝牙模式选择,按键消息使能等一些初始化操作,其中按键使能使得系统在有外部按键事件发生时及时响应,进行事件处理。

    REGISTER_LP_TARGET(app_hid_lp_target) = {
        .name = "app_hid_deal",
        .is_idle = app_hid_idle_query,
    };
    
    static const struct application_operation app_hid_ops = {
        .state_machine  = state_machine,
        .event_handler  = event_handler,
    };
    /*
     * 注册AT Module模式
     */
    REGISTER_APPLICATION(app_hid) = {
        .name   = "hid_key",
        .action = ACTION_KEYFOB,
        .ops    = &app_hid_ops,
        .state  = APP_STA_DESTROY,
    };
    

APP 事件处理机制:

  • 事件的产生与定义

    • 外部事件的数据采集在系统软件定时器的中断服务函数中完成,采集的数据将被打包为相应的“事件”周期性地上报至全局事件列表。

    • void sys_event_notify(struct sys_event *e); 此函数为事件通知函数,系统有事件发生时调用。

  • 事件的处理

    • 本案例中主要的事件处理包括连接事件处理、按键事件处理和LED事件处理,事件处理函数的共同入口都是event_handler().之后调用不同的函数实现不同类型事件的响应处理。

    • 蓝牙连接事件处理:在APP运行以后,首先进行的蓝牙连接事件处理,进行蓝牙初始化,HID描述符解读,蓝牙模式选择等,函数的第二个参数根据事件的不同,传入不同的事件类型,执行不同分支,如下(app_standard_keyboard.c):

      static int stdkb_event_handler(struct application *app, struct sys_event *event)
      {
          u8 i = 0;
      #if (TCFG_HID_AUTO_SHUTDOWN_TIME)
          //重置无操作定时计数
          if (event->type != SYS_DEVICE_EVENT || DEVICE_EVENT_FROM_POWER != event->arg) { //过滤电源消息
              //过滤掉sniff_enable or sniff_disable带来电源消息
              if (!(event->type == SYS_BT_EVENT && event->arg == DEVICE_EVENT_FROM_CON)) {
                  sys_timer_modify(g_auto_shutdown_timer, TCFG_HID_AUTO_SHUTDOWN_TIME * 1000);
              }
          }
      #endif
      
      #if TCFG_USER_EDR_ENABLE
          bt_comm_edr_sniff_clean();
      #endif
      
          /* log_info("event: %s", event->arg); */
          switch (event->type) {
          case SYS_KEY_EVENT:
              /* log_info("Sys Key : %s", event->arg); */
              stdkb_key_event_handler(event);
              return 0;
      
      • 以下代码为蓝牙连接事件处理函数,进行蓝牙初始化以及模式选择(app_keyfob.c)。

      static int stdkb_bt_connction_status_event_handler(struct bt_event *bt)
      {
          int ret = 0;
          log_info("----%s %d", __FUNCTION__, bt->event);
      
      
          switch (bt->event) {
          case BT_STATUS_INIT_OK:
              /*
               * 蓝牙初始化完成
               */
      
      • 调用 stdkb__event_handler(),stdkb_bt_connction_status_event_handler()函数实现蓝牙连接等事件。

    • 按键事件处理和 LED 事件处理:通过调用 app_keyfob_event_handler()函数进入按键事件处理流程,根据按键的类型和按键值进 入 app_key_deal_test()函数进行事件处理(app_standard_keyboard.c)。

      static void app_keyfob_event_handler(struct sys_event *event)
      {
          /* u16 cpi = 0; */
          u8 event_type = 0;
          u8 key_value = 0;
      
          if (event->arg == (void *)DEVICE_EVENT_FROM_KEY) {
              event_type = event->u.key.event;
              key_value = event->u.key.value;
              printf("app_key_evnet: %d,%d\n", event_type, key_value);
              app_keyfob_deal_test(event_type, key_value);
          }
      }
      
      static void app_keyfob_deal_test(u8 key_type, u8 key_value)
      {
          u16 key_msg = 0;
      
      #if TCFG_USER_EDR_ENABLE
          if (bt_hid_mode == HID_MODE_EDR && !edr_hid_is_connected()) {
              if (bt_connect_phone_back_start()) { //回连
                  return;
              }
          }
      #endif
      
          switch (key_type) {
      
      • 下图为 LED 工作状态部分实现函数(app_keyfob.c)。

      static void led_on_off(u8 state, u8 res)
      {
          /* if(led_state != state || (state == LED_KEY_HOLD)){ */
          if (1) { //相同状态也要更新时间
              u8 prev_state = led_state;
              log_info("led_state: %d>>>%d", led_state, state);
              led_state = state;
              led_io_flash = 0;
      
  • 按键事件处理和触摸板事件处理

    • 通过调用stdkb_key_event_handler()函数进入按键事件处理流程,根据按键的类型和按键值进入matrix_key_map_deal()或者stdkb_touch_pad_event_deal,函数进行事件处理。

    static void matrix_key_map_deal(u8 *map)
    {
        u8 row, col, i = 0;
        static u8 fn_press = 0;
    
        if (keyboard_system == SYSTEM_ARD) {
            if (special_key_deal(map, fn_remap_key, sizeof(fn_remap_key) / sizeof(special_key), fn_remap_event, 1)) {
                /* log_info("fn mark...\n"); */
                return;
            }
        }
    
    static void stdkb_touch_pad_event_deal(struct sys_event *event)
    {
        u8 mouse_report[8] = {0};
        if ((event->u).touchpad.gesture_event) {
            //g_printf("touchpad gesture_event:0x%x\n", (event->u).touchpad.gesture_event);
            switch ((event->u).touchpad.gesture_event) {
            case 0x1:
                mouse_report[0] |= _KEY_MOD_LMETA;
                mouse_report[2] = _KEY_EQUAL;
                hid_report_send(KEYBOARD_REPORT_ID, mouse_report, 8);
                memset(mouse_report, 0x0, 8);
                hid_report_send(KEYBOARD_REPORT_ID, mouse_report, 8);
                return;
            case 0x2:
    
  • 数据发送:

    • KEYBOARD属于HID设备范畴,数据的定义与发送要根据HID 设备描述符的内容进行确定,由下图的描述符可知,该描述符是一个用户自定义描述符,由KeyBoard、Consumer Control和Mouse组成, Keyboard主要实现普通按键的功能,Consumer Control实现多媒体系统控制, Mouse实现触摸板功能(app_standard_keyboard.c)。

          0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
          0x09, 0x06,        // Usage (Keyboard)
          0xA1, 0x01,        // Collection (Application)
          0x85, KEYBOARD_REPORT_ID,//   Report ID (1)
          0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
          0x19, 0xE0,        //   Usage Minimum (0xE0)
          0x29, 0xE7,        //   Usage Maximum (0xE7)
          0x15, 0x00,        //   Logical Minimum (0)
          0x25, 0x01,        //   Logical Maximum (1)
          0x75, 0x01,        //   Report Size (1)
          0x95, 0x08,        //   Report Count (8)
          0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
          0x95, 0x01,        //   Report Count (1)
          0x75, 0x08,        //   Report Size (8)
          0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
          0x95, 0x03,        //   Report Count (3)
          0x75, 0x01,        //   Report Size (1)
          0x05, 0x08,        //   Usage Page (LEDs)
          0x19, 0x01,        //   Usage Minimum (Num Lock)
          0x29, 0x03,        //   Usage Maximum (Scroll Lock)
          0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
          0x95, 0x05,        //   Report Count (5)
          0x75, 0x01,        //   Report Size (1)
          0x91, 0x01,        //   Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
          0x95, 0x06,        //   Report Count (6)
          0x75, 0x08,        //   Report Size (8)
          0x15, 0x00,        //   Logical Minimum (0)
          0x26, 0xFF, 0x00,  //   Logical Maximum (255)
          0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
          0x19, 0x00,        //   Usage Minimum (0x00)
          0x2A, 0xFF, 0x00,  //   Usage Maximum (0xFF)
          0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
          0xC0,              // End Collection
          0x05, 0x0C,        // Usage Page (Consumer)
          0x09, 0x01,        // Usage (Consumer Control)
          0xA1, 0x01,        // Collection (Application)
          0x85, COUSTOM_CONTROL_REPORT_ID,//   Report ID (3)
          0x75, 0x10,        //   Report Size (16)
          0x95, 0x01,        //   Report Count (1)
          0x15, 0x00,        //   Logical Minimum (0)
          0x26, 0x8C, 0x02,  //   Logical Maximum (652)
          0x19, 0x00,        //   Usage Minimum (Unassigned)
          0x2A, 0x8C, 0x02,  //   Usage Maximum (AC Send)
          0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
          0xC0,              // End Collection
      //
          // Dummy mouse collection starts here
          //
          0x05, 0x01,         // USAGE_PAGE (Generic Desktop)
          0x09, 0x02,         // USAGE (Mouse)
          0xa1, 0x01,         // COLLECTION (Application)
          0x85, MOUSE_POINT_REPORT_ID,     //   REPORT_ID (Mouse)
          0x09, 0x01,         //   USAGE (Pointer)
          0xa1, 0x00,         //   COLLECTION (Physical)
          0x05, 0x09,         //     USAGE_PAGE (Button)
          0x19, 0x01,         //     USAGE_MINIMUM (Button 1)
          0x29, 0x02,         //     USAGE_MAXIMUM (Button 2)
          0x15, 0x00,         //     LOGICAL_MINIMUM (0)
          0x25, 0x01,         //     LOGICAL_MAXIMUM (1)
          0x75, 0x01,         //     REPORT_SIZE (1)
          0x95, 0x02,         //     REPORT_COUNT (2)
          0x81, 0x02,         //     INPUT (Data,Var,Abs)
          0x95, 0x06,         //     REPORT_COUNT (6)
          0x81, 0x03,         //     INPUT (Cnst,Var,Abs)
          0x05, 0x01,         //     USAGE_PAGE (Generic Desktop)
          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, 0x06,         //     INPUT (Data,Var,Rel)
          0xc0,              //   END_COLLECTION
      0xc0              // END_COLLECTION
      
    • HID数据发送接口,根据当前连接模式来发送HID report。 app_standard_keyboard.c)。

      void hid_report_send(u8 report_id, u8 *data, u16 len)
      {
          if (bt_hid_mode == HID_MODE_EDR) {
      #if TCFG_USER_EDR_ENABLE
              edr_hid_data_send(report_id, data, len);
      #endif
          } else {
      #if TCFG_USER_BLE_ENABLE
              ble_hid_data_send(report_id, data, len);
      #endif
          }
      }