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 } }