prettyprint

2024年8月3日 星期六

[Raspberry Pi Pico W] A tiny but fully functional BLE Bluetooth keyboard using LVGL and Btstack libraries | |包含注音輸入鍵盤

 本篇文章介紹如何利用Raspberry Pi Pico W和LVGL Graphics library 與 Btstack bluetooth library,製做一個小型但又功能齊全的BLE藍芽鍵盤。

有關HID over GATT device的介紹,可參閱前篇:[Raspberry Pi Pico W] BTstack BLE Ep 4. HID over GATT -- Custom keypad HID device, 本篇文章著重在利用LVGL library製作完整功能的鍵盤。

本文章介紹的鍵盤分別有四縱模式: 小寫英文字、大寫英文字、數字與特殊符號和注音鍵盤。四種鍵盤分對應到LVGL keyboard widget: LV_KEYBOARD_MODE_TEXT_LOWER、LV_KEYBOARD_MODE_TEXT_UPPER、LV_KEYBOARD_MODE_SPECIAL and LV_KEYBOARD_MODE_USER_1。

小寫英文字、大寫英文字、數字與特殊符號鍵盤使用LVGL內定keyboard widgets。




注音輸入鍵盤則利用LV_KEYBOARD_MODE_USER_1模式,自製鍵盤時需要按鍵排例方式與每個按鍵控制模式:

  • 按鍵排例方式


  • 每個按鍵控制模式

使用
lv_obj_set_style_text_font(kb, &jf_font, LV_PART_ITEMS);//設定字形

lv_keyboard_set_map(kb, LV_KEYBOARD_MODE_USER_1, font_map, font_ctl_map);//設定LV_KEYBOARD_MODE_USER_1鍵盤

  • 注音鍵盤HID scan code對應表:
製作鍵盤按鍵對應到HID scan code, 其中modifier為0。

lv_obj_add_event_cb(lvgl_keyboard, lvgl_keyboard_cb, LV_EVENT_ALL, lvgl_keyboard);
使用上述指令加入按鍵的event callback function,處理收到按鍵按下時查詢按鍵的modifier and key_code 再透過HID report由藍芽輸出。其HID report格式如下所定義:
hid keyboard descriptor定義input data為:modifier(1 byte), reserved byte(1 byte)與keycode(6 bytes)。output data為:5 bits+3 bits。

指令:
send_hid_spec_code(uint8_t modifier, uint8_t keycode);
直接送出HID report,

key_input(uint8_t character);
查詢輸入character的modifier與keycode後再送出HID Report。

static int keycode_and_modifer_us_for_character(uint8_t character, uint8_t * keycode, uint8_t * modifier); //查詢character的modifier and keycode

三種keycode表如下
  • static const uint8_t keytable_scan_codes[]:
    小寫字母與沒有shift的特殊符號。
  • static const uint8_t keytable_scan_codes_shift[]:
    大寫字母與有shift的特殊符號。
  • static const char* keyboard_phonetic[]:
    注音符號keycode表,modifier為0。

詳細程式碼附於文章末尾。

成果影片:



程式碼:


pico_lvgl library: 
如何移植LVGL library到Raspberry Pi Pico,請參閱前篇文章介紹。

CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(picow_lvgl_hog_keyboard C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_lvgl_hog_keyboard 
                picow_lvgl_hog_keyboard.c
                lvgl_keyboard.c
                zh_TW_kb.c
                shortcuts.c
)

pico_set_program_name(picow_lvgl_hog_keyboard "picow_lvgl_bt_kb")
pico_set_program_version(picow_lvgl_hog_keyboard "0.1")

pico_enable_stdio_uart(picow_lvgl_hog_keyboard 1)
pico_enable_stdio_usb(picow_lvgl_hog_keyboard 0)

# Add the standard library to the build
target_link_libraries(picow_lvgl_hog_keyboard
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_lvgl_hog_keyboard PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(picow_lvgl_hog_keyboard 
        hardware_spi
        hardware_dma
        hardware_pio
        pico_util
        pico_cyw43_arch_none
        pico_btstack_cyw43
        pico_btstack_ble
        )

add_subdirectory(pico_lvgl)

target_link_libraries(picow_lvgl_hog_keyboard
        pico_lvgl
)
pico_btstack_make_gatt_header(picow_lvgl_hog_keyboard PRIVATE "${CMAKE_CURRENT_LIST_DIR}/picow_lvgl_hog_keyboard.gatt")

pico_add_extra_outputs(picow_lvgl_hog_keyboard)


lvgl_keyboard.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "pico_lvgl.h"
#include "usb_hid_scan_code.h"

extern const lv_font_t jf_font;

uint8_t lvgl_kb_left[4] = {239, 129, 147,0};
uint8_t lvgl_kb_right[4] = {239, 129, 148,0};
uint8_t lvgl_kb_ok[4] = {239, 128, 140,0};
uint8_t lvgl_kb_enter[4] = {239, 162, 162,0};
uint8_t lvgl_kb_bs[4] = {239, 149, 154,0};
uint8_t lvgl_kb_kb[4] = {239, 132, 156,0};


void set_zh_TW_keyboard(lv_obj_t *kb);
void shortcuts_init(lv_obj_t* parent);
void key_input(char character);
void send_hid_spec_code(uint8_t modifier, uint8_t keycode);


static uint8_t phonetic_selected=0;
void toggle_sel_key(lv_obj_t* kb) {

            if (phonetic_selected==0)  {
                phonetic_selected = 1;
                send_hid_spec_code(0, KEY_DOWN);
                for(int i=0; i < 10; i++) {
                    lv_btnmatrix_clear_btn_ctrl(kb,i,LV_BTNMATRIX_CTRL_HIDDEN);
                    lv_btnmatrix_set_btn_ctrl(kb,i, LV_KEYBOARD_CTRL_BTN_FLAGS|1);
                }
            }
            else{
                if (phonetic_selected == 1) {
                    send_hid_spec_code(0, KEY_UP);
                }
                for(int i=0; i < 10; i++) {
                    lv_btnmatrix_clear_btn_ctrl(kb,i,LV_KEYBOARD_CTRL_BTN_FLAGS|1);
                    lv_btnmatrix_set_btn_ctrl(kb,i, LV_BTNMATRIX_CTRL_HIDDEN);
                } 
                phonetic_selected=0;
            }
                
            
}
static bool lookup_phonetic_keycode(lv_obj_t *kb, uint8_t* key,  uint8_t * keycode){
    uint16_t size = 57;//sizeof(keyboard_phonetic);

    int i;
    for (i=0;i<size;i++){
        if (strcmp(keyboard_phonetic[i], key) !=0) continue;
        *keycode = i;
        return true;
    }
    if (key[0] >= '0' && key[0] <= '9'){
        key_input(key[0]);
        phonetic_selected++;
        toggle_sel_key(kb);
        
    }
    return false;
}

uint8_t lvgl_keyboard_mode;
void lvgl_keyboard_cb(lv_event_t* e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t* obj = lv_event_get_target(e);
    lv_obj_t * kb = lv_event_get_user_data(e);
    char key[5];
    if (code == LV_EVENT_CLICKED) {
        strcpy(key, lv_keyboard_get_btn_text(kb,lv_keyboard_get_selected_btn(kb)));
        if (strcmp(key, "ABC")==0) {
            lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_UPPER;
            return;
        } 
        if (strcmp(key, "abc") == 0) {
            lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_LOWER;
            return;
        }
        if (lv_keyboard_get_mode(kb) != LV_KEYBOARD_MODE_USER_1) {
            if ((lvgl_keyboard_mode == LV_KEYBOARD_MODE_TEXT_LOWER || lvgl_keyboard_mode == LV_KEYBOARD_MODE_TEXT_UPPER ) && 
                key[0] == '1') {
                lvgl_keyboard_mode = LV_KEYBOARD_MODE_SPECIAL;
                return;
            }
            if (strlen(key) == 1) {
                key_input(key[0]);
                return;
            }
        } else {
            static uint8_t keycode;
            if (lookup_phonetic_keycode(kb, key, &keycode)) {
                send_hid_spec_code(0, keycode);
                return;
            }
        }
        if (strcmp(key, lvgl_kb_enter)==0) {
            key_input(CHAR_RETURN);
            return;
        }
        if (strcmp(key, lvgl_kb_right)==0 || strcmp(key, "〉") == 0) {
            send_hid_spec_code(0, KEY_RIGHT);
            return;
        }
        if (strcmp(key, lvgl_kb_left)==0 || strcmp(key, "〈") == 0) {
            send_hid_spec_code(0, KEY_LEFT);
            return;
            }
        if (strcmp(key, lvgl_kb_bs)==0 || strcmp(key, "←")==0) {
            key_input(CHAR_BACKSPACE);
            return;
        }
        if (strcmp(key, "︿") == 0) {
            send_hid_spec_code(0, KEY_UP);
            return;
        }
        if (strcmp(key, "﹀") == 0) {
            send_hid_spec_code(0, KEY_DOWN);
            return;
        }
        if (strcmp(key, ",") == 0) {
            //for Windows
            //send_hid_spec_code(0, KEY_GRAVE);send_hid_spec_code(0, KEY_COMMA); 
            send_hid_spec_code(KEY_MOD_LSHIFT, KEY_COMMA);
            return;
        }
        if (strcmp(key, "。") == 0) {
            ///for Windows
            //send_hid_spec_code(0, KEY_GRAVE);send_hid_spec_code(0, KEY_DOT); 
            send_hid_spec_code(KEY_MOD_LSHIFT, KEY_DOT);
            return;
        }
        if (strcmp(key, lvgl_kb_ok)==0) {
            key_input(CHAR_RETURN);
            return;
        }
        if (strcmp(key, lvgl_kb_kb)==0){
            lv_obj_set_style_text_font(kb, &jf_font, LV_PART_ITEMS);
            lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_USER_1);
            send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;  
            return;              
        } 
        if (strcmp(key, "英")==0){
            lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
            lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_TEXT_LOWER);
            send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;  
            return;
        } 
        if (strcmp(key, "數")==0){
            lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
            lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_SPECIAL);
            send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;       
            return;       
        } 
        if (strcmp(key, "選字")==0){
            toggle_sel_key(kb);
            return;       
        }
    } 
}

void lvgl_keyboard_init(void)
{
    /*Create a keyboard to use it with an of the text areas*/
    lv_obj_t * lvgl_keyboard = lv_keyboard_create(lv_scr_act());

    lv_obj_t *shortcuts_frame = lv_obj_create(lv_scr_act());
    shortcuts_init(shortcuts_frame);
    lv_obj_set_size(shortcuts_frame, lv_pct(100), lv_pct(32));
    lv_obj_set_style_pad_all(shortcuts_frame, 3,0);
    lv_obj_set_style_bg_color(shortcuts_frame, lv_palette_main(LV_PALETTE_LIGHT_BLUE),0);

    set_zh_TW_keyboard(lvgl_keyboard);
    
    lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_LOWER;
    lv_obj_set_size(lvgl_keyboard, lv_pct(100), lv_pct(67));

    lv_obj_add_event_cb(lvgl_keyboard, lvgl_keyboard_cb, LV_EVENT_ALL, lvgl_keyboard);
}
pico_lvgl_hog_keyboard.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "btstack.h"

#include "ble/gatt-service/battery_service_server.h"
#include "ble/gatt-service/device_information_service_server.h"
#include "ble/gatt-service/hids_device.h"

#include "picow_lvgl_hog_keyboard.h"
#include "inttypes.h"
#include "pico_lvgl.h"

#include "usb_hid_scan_code.h"

extern const lv_font_t jf_font;


PIO TFT_PIO = pio0;
#define TFT_SM 0
#define TFT_SDI_GPIO 9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX=7, SCK=6, SIDE_SET

void lvgl_keyboard_init();

static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_packet_callback_registration_t sm_event_callback_registration;
static uint8_t battery = 100;
static hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID;
static uint8_t protocol_mode = HID_PROTOCOL_MODE_REPORT;

const uint8_t adv_data[] = {
    // Flags general discoverable, BR/EDR not supported
    0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
    // Name
    0x0d, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'H', 'I', 'D', ' ', 'K', 'e', 'y', 'b', 'o', 'a', 'r', 'd',
    //0x0d
    // 16-bit Service UUIDs
    0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 
                ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE & 0xff, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE >> 8,
    // Appearance HID - Keyboard (Category 15, Sub-Category 1)
    0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC1, 0x03,
};
const uint8_t adv_data_len = sizeof(adv_data);

// Buffer for 30 characters
static uint8_t key_input_storage[30];
static btstack_ring_buffer_t key_input_buffer;

// HID Keyboard lookup
static int lookup_keycode(uint8_t character, const uint8_t * table, int size, uint8_t * keycode){
    int i;
    for (i=0;i<size;i++){
        if (table[i] != character) continue;
        *keycode = i;
        return 1;
    }
    return 0;
}

static int keycode_and_modifer_us_for_character(uint8_t character, uint8_t * keycode, uint8_t * modifier){
    int found;
    found = lookup_keycode(character, keytable_scan_codes, sizeof(keytable_scan_codes), keycode);
    if (found) {
        *modifier = 0;  // none
        return 1;
    }
    found = lookup_keycode(character, keytable_scan_codes_shift, sizeof(keytable_scan_codes_shift), keycode);
    if (found) {
        *modifier = 2;  // shift
        return 1;
    }
    return 0;
}

// HID Report sending
static void send_report(int modifier, int keycode){
    uint8_t report[] = {  modifier, 0, keycode, 0, 0, 0, 0, 0};
    switch (protocol_mode){
        case 0:
            hids_device_send_boot_keyboard_input_report(con_handle, report, sizeof(report));
            break;
        case 1:
           hids_device_send_input_report(con_handle, report, sizeof(report));
           break;
        default:
            break;
    }
}

static enum {
    W4_INPUT,
    W4_CAN_SEND_FROM_BUFFER,
    W4_CAN_SEND_KEY_UP,
} state;


static void typing_can_send_now(void){
    switch (state){
        case W4_CAN_SEND_FROM_BUFFER:
            while (1){
                uint8_t c;
                uint32_t num_bytes_read;

                btstack_ring_buffer_read(&key_input_buffer, &c, 1, &num_bytes_read);
                if (num_bytes_read == 0){
                    state = W4_INPUT;
                    break;
                }

                uint8_t modifier;
                uint8_t keycode;
                int found = keycode_and_modifer_us_for_character(c, &keycode, &modifier);
                if (!found) continue;

                printf("sending: %c\n", c);

                send_report(modifier, keycode);
                state = W4_CAN_SEND_KEY_UP;
                hids_device_request_can_send_now_event(con_handle);
                break;
            }
            break;
        case W4_CAN_SEND_KEY_UP:
            send_report(0, 0);
            if (btstack_ring_buffer_bytes_available(&key_input_buffer)){
                state = W4_CAN_SEND_FROM_BUFFER;
                hids_device_request_can_send_now_event(con_handle);
            } else {
                state = W4_INPUT;
            }
            break;
        default:
            break;
    }
}

void send_hid_spec_code(uint8_t modifier, uint8_t keycode) {
    send_report(modifier, keycode);
    state = W4_CAN_SEND_KEY_UP;
    hids_device_request_can_send_now_event(con_handle);
}

void key_input(char character){
    uint8_t c = character;
    btstack_ring_buffer_write(&key_input_buffer, &c, 1);
    // start sending
    if (state == W4_INPUT && con_handle != HCI_CON_HANDLE_INVALID){
        state = W4_CAN_SEND_FROM_BUFFER;
        hids_device_request_can_send_now_event(con_handle);
    }
}

static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
    UNUSED(channel);
    UNUSED(size);

    if (packet_type != HCI_EVENT_PACKET) return;

    switch (hci_event_packet_get_type(packet)) {
        case HCI_EVENT_DISCONNECTION_COMPLETE:
            con_handle = HCI_CON_HANDLE_INVALID;
            printf("Disconnected\n");
            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 SM_EVENT_NUMERIC_COMPARISON_REQUEST:
            printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
            sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
            break;
        case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
            printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
            break;
        case HCI_EVENT_HIDS_META:
            switch (hci_event_hids_meta_get_subevent_code(packet)){
                case HIDS_SUBEVENT_INPUT_REPORT_ENABLE:
                    con_handle = hids_subevent_input_report_enable_get_con_handle(packet);
                    printf("Report Characteristic Subscribed %u\n", hids_subevent_input_report_enable_get_enable(packet));
                    break;
                case HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE:
                    con_handle = hids_subevent_boot_keyboard_input_report_enable_get_con_handle(packet);
                    printf("Boot Keyboard Characteristic Subscribed %u\n", hids_subevent_boot_keyboard_input_report_enable_get_enable(packet));
                    break;
                case HIDS_SUBEVENT_PROTOCOL_MODE:
                    protocol_mode = hids_subevent_protocol_mode_get_protocol_mode(packet);
                    printf("Protocol Mode: %s mode\n", hids_subevent_protocol_mode_get_protocol_mode(packet) ? "Report" : "Boot");
                    break;
                case HIDS_SUBEVENT_CAN_SEND_NOW:
                    typing_can_send_now();
                    break;
                default:
                    break;
            }
            break;
            
        default:
            break;
    }
}

void hog_keyboard_init() {
    //1. initialize ring buffer for key input
    btstack_ring_buffer_init(&key_input_buffer, key_input_storage, sizeof(key_input_storage));
    
    //2. l2cap initialize
    l2cap_init();

    //3. setup SM: Display only
    sm_init();
    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
    sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);

    //4. setup ATT server
    att_server_init(profile_data, NULL, NULL);

    //5. setup battery service
    battery_service_server_init(battery);

    //6. setup device information service
    device_information_service_server_init();

    //7. setup HID Device service
    hids_device_init(0, hid_descriptor_keyboard_boot_mode, sizeof(hid_descriptor_keyboard_boot_mode));

    //8. setup advertisements
    uint16_t adv_int_min = 0x0030;
    uint16_t adv_int_max = 0x0030;
    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);
    gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data);
    gap_advertisements_enable(1);

    //9. register for HCI events
    hci_event_callback_registration.callback = &packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);

    //10. register for SM events
    sm_event_callback_registration.callback = &packet_handler;
    sm_add_event_handler(&sm_event_callback_registration);

    //11. register for HIDS
    hids_device_register_packet_handler(packet_handler);

    hci_power_control(HCI_POWER_ON);
}

int main()
{
    stdio_init_all();

    if (cyw43_arch_init()) {
        printf("cyw43_arch_init error\n");
        return 0;
    }
    //0. initialize keyboard & lvgl TFT
     pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO);

    pico_lvgl_display_init(5);
    pico_lvgl_xpt2046_init();
    //pico_lvgl_encoder_init(false);
  
    lvgl_keyboard_init();
 
    hog_keyboard_init();

    uint8_t c;
    while(1) {
        lv_timer_handler();
        sleep_ms(1);
        if (con_handle == HCI_CON_HANDLE_INVALID) continue;       
    }

    return 0;
}

pico_lvgl_hog_keyboard.gatt
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "PicoW BLE HID Keyboard"
CHARACTERISTIC, GATT_DATABASE_HASH, READ,

// add Battery Service
#import <battery_service.gatt>

// add Device ID Service
#import <device_information_service.gatt>

// add HID Service
#import <hids.gatt>



shortcuts.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "pico_lvgl.h"
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_A 0x04 // Keyboard a and A
#define KEY_B 0x05 // Keyboard b and B
#define KEY_C 0x06 // Keyboard c and C
#define KEY_D 0x07 // Keyboard d and D
#define KEY_E 0x08 // Keyboard e and E

#define KEY_MOD_LCTRL  0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT   0x04
#define KEY_MOD_LMETA  0x08
#define KEY_MOD_RCTRL  0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT   0x40
#define KEY_MOD_RMETA  0x80

#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
#define KEY_TAB 0x2b // Keyboard Tab
#define KEY_SPACE 0x2c // Keyboard Spacebar

void send_hid_spec_code(uint8_t modifier, uint8_t keycode);

static const char* shortcuts_map[] = {
    LV_SYMBOL_HOME "Home",  "<Back", LV_SYMBOL_OK "Select", LV_SYMBOL_PREV "Prev",LV_SYMBOL_NEXT "Next", "\n",
    "ESC", "Tab", LV_SYMBOL_CLOSE "Close", "Ctrl+SP",""
};
static const uint16_t KB_BTN = LV_KEYBOARD_CTRL_BTN_FLAGS;
static const uint16_t KB_HIDDEN = LV_BTNMATRIX_CTRL_HIDDEN;

static const uint16_t shortcuts_ctl_map[] = {
    KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
    KB_BTN,KB_BTN,KB_BTN,KB_BTN|2
};

static void shortcuts_btnmatrix_cb(lv_event_t* e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t* obj = lv_event_get_user_data(e);
    uint16_t select_btn;
    if (code == LV_EVENT_CLICKED) {
        select_btn = lv_btnmatrix_get_selected_btn(obj);
        switch(select_btn) {
            case 0:
            send_hid_spec_code(0, KEY_F1);
            break;
            case 1:
            send_hid_spec_code(0, KEY_F2);
            break;
            case 2:
            send_hid_spec_code(0, KEY_F3);
            break;
            case 3:
            send_hid_spec_code(0, KEY_F4);
            break;
            case 4:
            send_hid_spec_code(0, KEY_F5);
            break;

            case 5:
            send_hid_spec_code(0, KEY_ESC);
            break;
            case 6:
            send_hid_spec_code(0, KEY_TAB);
            break;
            case 7:
            send_hid_spec_code(KEY_MOD_LALT, KEY_F4);
            break;
            case 8:
            send_hid_spec_code(KEY_MOD_LCTRL, KEY_SPACE);
            break;
        }
    }
}
void shortcuts_init(lv_obj_t* parent) {
    lv_obj_t *shortcuts = lv_btnmatrix_create(parent);
    lv_btnmatrix_set_map(shortcuts, shortcuts_map);
    lv_btnmatrix_set_btn_ctrl_all(shortcuts, KB_BTN);
    lv_obj_set_size(shortcuts, lv_pct(100), lv_pct(100));
    lv_obj_set_style_pad_all(shortcuts, 0,0);
    lv_obj_add_event_cb(shortcuts, shortcuts_btnmatrix_cb, LV_EVENT_ALL, shortcuts);
}
usb_hid_scan_code.h
#ifndef __USB_HID_SCAN_CODE_H__
#define __USB_HID_SCAN_CODE_H__

#include "stdio.h"
#include "pico/stdlib.h"
/**
 * Modifier masks - used for the first byte in the HID report.
 * NOTE: The second byte in the report is reserved, 0x00
 */
#define KEY_MOD_LCTRL  0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT   0x04
#define KEY_MOD_LMETA  0x08
#define KEY_MOD_RCTRL  0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT   0x40
#define KEY_MOD_RMETA  0x80


#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF 0x01 //  Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
// 0x02 //  Keyboard POST Fail
// 0x03 //  Keyboard Error Undefined

#define SPECIAL_KEY 0xff
#define CHAR_RETURN     '\n'
#define CHAR_ESCAPE      27
#define CHAR_TAB         '\t'
#define CHAR_BACKSPACE   0x7f

#define KEY_DELETE 0x4c // Keyboard Delete Forward
#define KEY_RIGHT 0x4f // Keyboard Right Arrow
#define KEY_LEFT 0x50 // Keyboard Left Arrow
#define KEY_DOWN 0x51 // Keyboard Down Arrow
#define KEY_UP 0x52 // Keyboard Up Arrow

// special keys
#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_F6 0x3f // Keyboard F6
#define KEY_F7 0x40 // Keyboard F7
#define KEY_F8 0x41 // Keyboard F8
#define KEY_F9 0x42 // Keyboard F9
#define KEY_F10 0x43 // Keyboard F10
#define KEY_F11 0x44 // Keyboard F11
#define KEY_F12 0x45 // Keyboard F12

#define KEY_GRAVE 0x35 // Keyboard ` and ~
#define KEY_COMMA 0x36 // Keyboard , and <
#define KEY_DOT 0x37 // Keyboard . and >



static const uint8_t keytable_scan_codes[] = {
    SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,'a','b','c','d','e','f','g','h','i','j','k','l', //0x00~0x0F
    'm','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2', //0x10~0x1F
    '3','4','5','6','7','8','9','0',CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB,' ','-','=','[', //0x20~0x2F
    ']','\\',SPECIAL_KEY,';','\'','`',',','.','/',
};

static const uint8_t keytable_scan_codes_shift[] = {
    SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,'A','B','C','D','E','F','G','H','I','J','K','L', //0x00~0x0F
    'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','!','@', //0x10~0x1F
    '#','$','%','^','&','*','(',')',CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB,' ','_','+','{', //0x20~0x2F
    '}','|',SPECIAL_KEY,':','"','~','<','>','?',
};

static const char ILLEGAL_PHONEETIC[] = "000";
static const char* keyboard_phonetic[] = {
    ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,
    "ㄇ","ㄖ","ㄏ","ㄎ","ㄍ","ㄑ","ㄕ","ㄘ","ㄛ","ㄨ","ㄜ","ㄠ", //0x00~0x0F

    "ㄩ","ㄙ","ㄟ","ㄣ","ㄆ","ㄐ","ㄋ","ㄔ","ㄧ","ㄒ","ㄊ","ㄌ","ㄗ","ㄈ","ㄅ","ㄉ", //0x10~0x1F

    "ˇ","ˋ","ㄓ","ˊ","˙","ㄚ","ㄞ","ㄢ","換行", ILLEGAL_PHONEETIC, "←", ILLEGAL_PHONEETIC,
    " ","ㄦ","+","{", //0x20~0x2F

    "}","|",ILLEGAL_PHONEETIC,"ㄤ","\"","~","ㄝ","ㄡ","ㄥ" //0x30~0x38
};

#define REPORT_ID 0x01

// from USB HID Specification 1.1, Appendix B.1
static const uint8_t hid_descriptor_keyboard_boot_mode[] = {

    0x05, 0x01,                    // Usage Page (Generic Desktop)
    0x09, 0x06,                    // Usage (Keyboard)
    0xa1, 0x01,                    // Collection (Application)

    0x85,  0x01,                   // Report ID 1

    // Modifier byte

    0x75, 0x01,                    //   Report Size (1)
    0x95, 0x08,                    //   Report Count (8)
    0x05, 0x07,                    //   Usage Page (Key codes)
    0x19, 0xe0,                    //   Usage Minimum (Keyboard LeftControl)
    0x29, 0xe7,                    //   Usage Maxium (Keyboard Right GUI)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x25, 0x01,                    //   Logical Maximum (1)
    0x81, 0x02,                    //   Input (Data, Variable, Absolute)

    // Reserved byte

    0x75, 0x01,                    //   Report Size (1)
    0x95, 0x08,                    //   Report Count (8)
    0x81, 0x03,                    //   Input (Constant, Variable, Absolute)

    // LED report + padding

    0x95, 0x05,                    //   Report Count (5)
    0x75, 0x01,                    //   Report Size (1)
    0x05, 0x08,                    //   Usage Page (LEDs)
    0x19, 0x01,                    //   Usage Minimum (Num Lock)
    0x29, 0x05,                    //   Usage Maxium (Kana)
    0x91, 0x02,                    //   Output (Data, Variable, Absolute)

    0x95, 0x01,                    //   Report Count (1)
    0x75, 0x03,                    //   Report Size (3)
    0x91, 0x03,                    //   Output (Constant, Variable, Absolute)

    // Keycodes

    0x95, 0x06,                    //   Report Count (6)
    0x75, 0x08,                    //   Report Size (8)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x25, 0xff,                    //   Logical Maximum (1)
    0x05, 0x07,                    //   Usage Page (Key codes)
    0x19, 0x00,                    //   Usage Minimum (Reserved (no event indicated))
    0x29, 0xff,                    //   Usage Maxium (Reserved)
    0x81, 0x00,                    //   Input (Data, Array)

    0xc0,                          // End collection
};


#endif
zh_TW.kb.c
#include "pico/stdlib.h"
#include "stdio.h"
#include "pico_lvgl.h"
#include "jf_font.h"

extern uint8_t lvgl_kb_left[4];
extern uint8_t lvgl_kb_right[4];

const char* font_map[] = {
    "1","2","3","4","5","6","7","8","9","0","選字","\n",
    "ㄅ","ㄉ","ˇ", "ˋ","ㄓ","ˊ", "˙","ㄚ","ㄞ","ㄢ","ㄦ","\n",
    "ㄆ","ㄊ","ㄍ","ㄐ","ㄔ","ㄗ","ㄧ","ㄛ","ㄟ","ㄣ","←","\n",
    "ㄇ","ㄋ","ㄎ","ㄑ","ㄕ","ㄘ","ㄨ","ㄜ","ㄠ","ㄤ","換行","\n",
    "ㄈ","ㄌ","ㄏ","ㄒ","ㄖ","ㄙ","ㄩ","ㄝ","ㄡ","ㄥ",",","\n",
   "英","數","〈"," ","〉","﹀","︿","。",""
};

const uint16_t KB_BTN = LV_KEYBOARD_CTRL_BTN_FLAGS|1;
const uint16_t KB_HIDDEN = LV_BTNMATRIX_CTRL_HIDDEN | 1;
const uint16_t space_key_ctl = LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_NO_REPEAT|2;

lv_btnmatrix_ctrl_t font_ctl_map[] = {
    KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_BTN,
    KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
    KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,LV_BTNMATRIX_CTRL_CHECKED|2,
    KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,LV_BTNMATRIX_CTRL_CHECKED|2,
    KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
    LV_BTNMATRIX_CTRL_CHECKED|2,LV_BTNMATRIX_CTRL_CHECKED|2,KB_BTN,KB_BTN|6,KB_BTN,KB_BTN,KB_BTN, KB_BTN
};

void set_zh_TW_keyboard(lv_obj_t *kb) {
    lv_obj_set_style_text_font(kb, &jf_font, LV_PART_ITEMS);
    lv_keyboard_set_map(kb, LV_KEYBOARD_MODE_USER_1, font_map, font_ctl_map);
    lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
    
}



沒有留言:

張貼留言