prettyprint

2023年6月26日 星期一

[Raspberry Pi Pico W] BTstack: Ep 4. HID device, Custom Bluetooth HID keypad

 在上篇將有線鍵盤透過Raspberry Pi Pico W轉換成無線藍芽鍵盤,

HID device, Convert a USB Cabled Keyboard to a Bluetooth Wireless Keyboard


本篇文章整合4x4 keypad pio程式,自訂一個藍芽鍵盤,如下圖。
  1. 4x4 keypad連接pico w pin 8 ~ pin 15,輸入如key pad上的內容。
  2. 左邊按鈕送出"F1" 功能鍵, key cod為0x3a。
  3. 右邊按鈕送出一串文字。
hid report包含modifier與scan code如下圖所示。
其他詳細文件說明,請參閱前一篇。

本實驗直接使用BTstack example程式hid_keyboard_demo.c。將363行註解。
另外製作送出keypad按鍵與特殊鍵功能。
詳細程式碼附於文末。

成果影片

  • Rebuild this project step by step:
  1. Generate the initial project structure:
    Download pico_project.py from https://github.com/raspberrypi/pico-project-generator
    $ pico_project.py --gui
  2. Copy the 4x4 keypad library:
    Copy (2)keypad.c (3)keypad.h (4)keypad.pio (5)CMakeLists.txt files to pico_keypad directory.
  3. Copy the (1)BT_custom_keypad.c to project directory
  4. Copy (6)btstack_config.h to the project root directory
  5. Copy hid_keyboard_demo.c in pico-sdk/lib/btstack/example folder to the project root directory, and comment out line 363[//demo_text_timer_hanlder(NULL)].
  6. Add the required libraries into CMakeLists.txt in root directory.

  • 程式碼:



(1) main code:BT_custom_keypad.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "hid_keyboard_demo.c"
#include "keypad.h"

#define F1_BUTTON   16
#define TYPE_STRING_BUTTON 17

void send_hid_text(uint8_t* text) {
    btstack_ring_buffer_write(&send_buffer, text, strlen(text));
    send_next(NULL);  
    busy_wait_ms(100);      
}

void send_hid_char(uint8_t c) {
    btstack_ring_buffer_write(&send_buffer, (uint8_t*)&c, 1);
    send_next(NULL);
    busy_wait_ms(100);
}

void send_hid_spec_code(uint8_t m, uint8_t c) {
    send_modifier = m;
    send_keycode = c;
    hid_device_request_can_send_now_event(hid_cid);
}

void key_button_callback(uint gpio, uint32_t events) {
    gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, false);
    gpio_acknowledge_irq(gpio, events);
    
    if (gpio == F1_BUTTON ) {       
        busy_wait_ms(100);     
        send_hid_spec_code(0, 0x3a); //F1 key
        gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, true);
    }
    if (gpio == TYPE_STRING_BUTTON) {
        busy_wait_ms(100);
        send_hid_text("messages...\n");
        gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, true);
    }

}
int main()
{
    stdio_init_all();
    if (cyw43_arch_init()) {
        printf("cyw43 arch init error\n");
        return 0;
    }

    gpio_init(F1_BUTTON);
    gpio_init(TYPE_STRING_BUTTON);
    gpio_pull_up(F1_BUTTON);
    gpio_pull_up(TYPE_STRING_BUTTON);
    gpio_set_irq_enabled_with_callback(F1_BUTTON, GPIO_IRQ_EDGE_FALL, true, key_button_callback);
    gpio_set_irq_enabled_with_callback(TYPE_STRING_BUTTON, GPIO_IRQ_EDGE_FALL, true, key_button_callback);
    keypad_init();
   
    btstack_main(0, NULL);
    gap_set_local_name("PicoW HID Keypad");

   
    uint8_t c;
    while(1) {
        c=get_new_keypad_value();
        
        if (c) {
            send_hid_char(c);
        } else {
            sleep_ms(10);
        }
        
        tight_loop_contents();
    }

    return 0;
}

(2) keypad.c
 #include "keypad.pio.h"
#include "keypad.h"
#include "hardware/clocks.h"
#include "stdio.h"
#include "pico/stdlib.h"


static uint8_t key_value=0;

static const PIO keypad_pio=pio1;
static const uint keypad_sm=0;
static const uint8_t keys[4][4]={
    {'1','2','3','A'},
    {'4','5','6','B'},
    {'7','8','9','C'},
    {'*','0','#','D'}
    };

static void keypad_handle() {
    if (pio_interrupt_get(keypad_pio, 0)) {
        key_value=0;
        pio_interrupt_clear(keypad_pio, 0);
        uint32_t x, y;
          y=pio_sm_get_blocking(keypad_pio, keypad_sm);
        x=pio_sm_get_blocking(keypad_pio, keypad_sm);

        for(uint8_t i = 0 ; i < 4; i++){
            if ((x >> i)==1) {x=i;break;}
        }
        for(uint8_t j = 0 ; j < 4; j++){
            if ((y >> j)==1) {y=j;break;}
        }
        key_value = keys[x][y];
    }
}

static void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq) {
    uint offset=0;
    pio_sm_config c;
    offset = pio_add_program(pio, &keypad_program);
    c = keypad_program_get_default_config(offset);
    
    for (int i=0; i < 4; i++) pio_gpio_init(pio, in_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base, 4, false);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);

    sm_config_set_in_pins(&c, in_base);
    sm_config_set_set_pins(&c, set_base, 4);
   
    sm_config_set_in_shift(&c, false, false, 32);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);

    uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    irq_add_shared_handler(pio_irq, keypad_handle, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq, true);

    pio_sm_init(pio, sm, offset, &c);

    pio_sm_set_enabled(pio, sm, true);
}
uint8_t get_new_keypad_value() {
    uint8_t ret_vale = key_value;
    key_value=0;
    return ret_vale;
}
void keypad_init() {
    keypad_pio_init(keypad_pio, keypad_sm,  8, 12, 2000);
}
(3) keypad.h
 #ifndef __KEYPAD_H
#define __KEYPAD_H


void keypad_init();
uint8_t get_new_keypad_value();

#endif
(4) keypad.pio
 .program keypad
.wrap_target
set_row_1:
set pins, 1 [31]  ;set row_1 High and wait for button bounce
set x,0x1
in pins,4
mov y, isr
jmp !y set_row_2
jmp rx_fifo
    
set_row_2:
set pins, 2  [31]
set x,0x2 
in pins,4  
mov y, isr  
jmp !y set_row_3  
jmp rx_fifo   

set_row_3: 
set pins, 4   [31]
set x,0x4  
in pins,4  
mov y, isr  
jmp !y  set_row_4  
jmp rx_fifo

set_row_4:
set pins, 8   [31]
set x,0x8  
in pins,4  
mov y, isr  
jmp !y  set_row_1

rx_fifo:  
push                  ;push y col   
in x,4  [2]
push                  ;and then x row  
irq  0 rel

wait 0 pin 0     ; check whether key is released
wait 0 pin 1  
wait 0 pin 2  
wait 0 pin 3  
.wrap
(5) CMakeLists.txt(keypad)
 add_library(pico_keypad INTERFACE)
pico_generate_pio_header(pico_keypad ${CMAKE_CURRENT_LIST_DIR}/keypad.pio)
target_sources(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/keypad.c
)

target_include_directories(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(pico_keypad INTERFACE
        hardware_pio
)
(6)btstack_config.h
#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
#define _PICO_BTSTACK_BTSTACK_CONFIG_H

// BTstack features that can be enabled
#ifdef ENABLE_BLE
#define ENABLE_LE_PERIPHERAL
#define ENABLE_LE_CENTRAL
#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE
#endif

#ifdef ENABLE_MESH
// Mesh Config
#define ENABLE_MESH_ADV_BEARER
#define ENABLE_MESH_GATT_BEARER
#define ENABLE_MESH_PB_ADV
#define ENABLE_MESH_PB_GATT
#define ENABLE_MESH_PROXY_SERVER
#define ENABLE_MESH_RELAY

#define ENABLE_MESH_PROVISIONER

#define MAX_NR_MESH_SUBNETS            2
#define MAX_NR_MESH_TRANSPORT_KEYS    16
#define MAX_NR_MESH_VIRTUAL_ADDRESSES 16
// allow for one NetKey update
#define MAX_NR_MESH_NETWORK_KEYS      (MAX_NR_MESH_SUBNETS+1)
#endif 


#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
#define ENABLE_PRINTF_HEXDUMP
#define ENABLE_SCO_OVER_HCI

// BTstack configuration. buffers, sizes, ...
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
#define HCI_ACL_PAYLOAD_SIZE (1691 + 4)
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
#define MAX_NR_AVDTP_CONNECTIONS 1
#define MAX_NR_AVDTP_STREAM_ENDPOINTS 1
#define MAX_NR_AVRCP_CONNECTIONS 2
#define MAX_NR_BNEP_CHANNELS 1
#define MAX_NR_BNEP_SERVICES 1
#define MAX_NR_BTSTACK_LINK_KEY_DB_MEMORY_ENTRIES  2
#define MAX_NR_GATT_CLIENTS 1
#define MAX_NR_HCI_CONNECTIONS 2
#define MAX_NR_HID_HOST_CONNECTIONS 1
#define MAX_NR_HIDS_CLIENTS 1
#define MAX_NR_HFP_CONNECTIONS 1
#define MAX_NR_L2CAP_CHANNELS  4
#define MAX_NR_L2CAP_SERVICES  3
#define MAX_NR_RFCOMM_CHANNELS 1
#define MAX_NR_RFCOMM_MULTIPLEXERS 1
#define MAX_NR_RFCOMM_SERVICES 1
#define MAX_NR_SERVICE_RECORD_ITEMS 4
#define MAX_NR_SM_LOOKUP_ENTRIES 3
#define MAX_NR_WHITELIST_ENTRIES 16
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16

// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
#define MAX_NR_CONTROLLER_SCO_PACKETS 3

// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
#define HCI_HOST_ACL_PACKET_LEN 1024
#define HCI_HOST_ACL_PACKET_NUM 3
#define HCI_HOST_SCO_PACKET_LEN 120
#define HCI_HOST_SCO_PACKET_NUM 3

// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
#define NVM_NUM_DEVICE_DB_ENTRIES 16
#define NVM_NUM_LINK_KEYS 16

// We don't give btstack a malloc, so use a fixed-size ATT DB.
#define MAX_ATT_DB_SIZE 512

// BTstack HAL configuration
#define HAVE_EMBEDDED_TIME_MS

// map btstack_assert onto Pico SDK assert()
#define HAVE_ASSERT

// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
#define HCI_RESET_RESEND_TIMEOUT_MS 1000

#define ENABLE_SOFTWARE_AES128
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS

//#define HAVE_BTSTACK_STDIN

// To get the audio demos working even with HCI dump at 115200, this truncates long ACL packets
//#define HCI_DUMP_STDOUT_MAX_SIZE_ACL 100

#ifdef ENABLE_CLASSIC
#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE
#endif

#define HAVE_MALLOC

#endif // _PICO_BTSTACK_BTSTACK_CONFIG_H

沒有留言:

張貼留言