本篇文章介紹HID over Gatt(HOG) bluetooth mouse. 使用Btstack library. 使用者介面為觸控螢幕並使用LVGL graphic library。
軟體部份:
有關hog mouse主要修改自Btstack example hog_mouse_demo.c程式,詳細內容參閱文末的hog_mouse.h檔案。
HID report
LVGL library移植至Raspberry Pi Pico程式碼如前篇文章所示:
[Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)
在本文章中修改部份程式碼,附於文末。
成果展示:
程式碼:
- pico_tft.c
#include "stdio.h" #include "stdlib.h" #include "pico/stdlib.h" #include "hardware/clocks.h" #include "string.h" #include "registers.h" #include "pico_tft.pio.h" #include "pico_tft.h" #include "hardware/dma.h" #define MAX_BYTE_TRANS (TFT_WIDTH*TFT_HEIGHT*2) #define PICO_TFT_SERIAL #define PICO_TFT_DMA // MADCTL register: MY,MX,MV,ML,BGR,MH,x,x static uint8_t TFT_MADCTL_PORTRAIT = 0b01001000; static uint8_t TFT_MADCTL_LANDSCAPE = 0b00101000; static uint8_t TFT_MADCTL_PORTRAIT_MIRROR = 0b10001000; static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 0b11101000; static uint16_t tft_width; static uint16_t tft_height; static uint8_t tft_orientation; PIO tft_pio = pio1; uint tft_sm=0; uint in_out_base_pin=4; uint set_base_pin=12; uint sideset_base_pin=16; uint s_in_out_base_pin=19; int tft_dma_channel; void tft_cmd(uint32_t cmd, uint32_t count, uint8_t *param) { pio_sm_restart(tft_pio, tft_sm); #ifdef PICO_TFT_SERIAL pio_sm_put_blocking(tft_pio, tft_sm, cmd << 24); #endif #ifdef PICO_TFT_PARALLEL pio_sm_put_blocking(tft_pio, tft_sm, cmd); #endif pio_sm_put_blocking(tft_pio, tft_sm, count); for (int i = 0; i < count; i++) { #ifdef PICO_TFT_SERIAL pio_sm_put_blocking(tft_pio, tft_sm, param[i]<<24); #endif #ifdef PICO_TFT_PARALLEL pio_sm_put_blocking(tft_pio, tft_sm, param[i]); #endif } } //#ifdef PICO_TFT_DMA void tft_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param) { #ifdef PICO_TFT_SERIAL tft_cmd(cmd, count, param); return; #endif pio_sm_restart(tft_pio, tft_sm); pio_sm_put_blocking(tft_pio, tft_sm, cmd); pio_sm_put_blocking(tft_pio, tft_sm, count); dma_channel_set_trans_count(tft_dma_channel, count >> DMA_SIZE_8, false); dma_channel_set_read_addr(tft_dma_channel, param, false); dma_channel_start(tft_dma_channel); dma_channel_wait_for_finish_blocking(tft_dma_channel); } //#endif void tft_pio_cmd_init(PIO pio, uint sm, uint in_out_base, uint set_sideset, uint32_t freq) { uint offset=0; pio_sm_config c; #ifdef PICO_TFT_PARALLEL offset = pio_add_program(pio, &tft_pio_parallel_program); c = tft_pio_parallel_program_get_default_config(offset); for (int i=0; i < 8; i++) pio_gpio_init(pio, in_out_base+i); for (int i=0; i < 4; i++) pio_gpio_init(pio, set_sideset+i); pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 8, true); pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true); sm_config_set_in_pins(&c, in_out_base); sm_config_set_out_pins(&c, in_out_base, 8); sm_config_set_set_pins(&c, set_sideset, 4); sm_config_set_out_shift(&c, true, false, 8); sm_config_set_in_shift(&c, false, false, 8); #endif #ifdef PICO_TFT_SERIAL offset = pio_add_program(pio, &tft_pio_serial_program); c = tft_pio_serial_program_get_default_config(offset); pio_gpio_init(pio, in_out_base); for (int i=0; i < 3; i++) pio_gpio_init(pio, set_sideset+i); pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 1, true); pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 3, true); sm_config_set_in_pins(&c, in_out_base); sm_config_set_out_pins(&c, in_out_base, 1); sm_config_set_sideset_pins(&c, set_sideset); sm_config_set_out_shift(&c, false, false, 8); sm_config_set_in_shift(&c, true, false, 8); #endif //sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); float div = clock_get_hz(clk_sys)/freq; sm_config_set_clkdiv(&c, div); //sm_config_set_clkdiv(&c, 1.25); #ifdef PICO_TFT_DMA /* DMA */ tft_dma_channel = dma_claim_unused_channel(true); dma_channel_config dc = dma_channel_get_default_config(tft_dma_channel); channel_config_set_write_increment(&dc, false); channel_config_set_read_increment(&dc, true); channel_config_set_dreq(&dc, pio_get_dreq(pio, sm, true)); channel_config_set_transfer_data_size(&dc, DMA_SIZE_8); //DMA_SIZE_8,16,32 dma_channel_configure(tft_dma_channel, &dc, (void*) (PIO1_BASE+PIO_TXF0_OFFSET), NULL, MAX_BYTE_TRANS>> DMA_SIZE_8, false); //DMA_SIZE_8 or 16 or 32 /* DMA */ #endif pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } /* tft draw functions*/ uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B) { uint16_t c; c = (((uint16_t)R)>>3)<<11 | (((uint16_t)G)>>2) << 5 | ((uint16_t)B)>>3; return c; } void tft_memory_write_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { uint8_t addr[4]; addr[0]=(uint8_t)(x1 >> 8); addr[1]= (uint8_t)(x1 & 0xff); addr[2]= (uint8_t)(x2 >> 8); addr[3]= (uint8_t)(x2 & 0xff); tft_cmd(TFT_COLADDRSET, 4, addr); addr[0]=(uint8_t)(y1 >> 8); addr[1]= (uint8_t)(y1 & 0xff); addr[2]= (uint8_t)(y2 >> 8); addr[3]= (uint8_t)(y2 & 0xff); tft_cmd(TFT_PAGEADDRSET, 4, addr ); tft_cmd(TFT_MEMORYWRITE, 0, NULL); } void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { uint8_t addr[4]; addr[0]=(uint8_t)(x1 >> 8); addr[1]= (uint8_t)(x1 & 0xff); addr[2]= (uint8_t)(x2 >> 8); addr[3]= (uint8_t)(x2 & 0xff); tft_cmd(TFT_COLADDRSET, 4, addr); addr[0]=(uint8_t)(y1 >> 8); addr[1]= (uint8_t)(y1 & 0xff); addr[2]= (uint8_t)(y2 >> 8); addr[3]= (uint8_t)(y2 & 0xff); tft_cmd(TFT_PAGEADDRSET, 4, addr ); } /* put color at point*/ void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color) { if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) { printf("over range,x,y\n"); return; } tft_set_address_window(x,y,x,y); tft_cmd(TFT_MEMORYWRITE, 2, (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color}); } uint16_t tft_get_width() { return tft_width; } uint16_t tft_get_height() { return tft_height; } uint8_t tft_get_orientation() { return tft_orientation; } void tft_set_orientation(uint8_t orientation) { tft_orientation = orientation; switch (orientation) { case TFT_ORIENTATION_PORTRAIT: tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){TFT_MADCTL_PORTRAIT}); tft_width = TFT_WIDTH; tft_height = TFT_HEIGHT; break; case TFT_ORIENTATION_PORTRAIT_MIRROR: tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){TFT_MADCTL_PORTRAIT_MIRROR}); tft_width = TFT_WIDTH; tft_height = TFT_HEIGHT; break; case TFT_ORIENTATION_LANDSCAPE: tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){TFT_MADCTL_LANDSCAPE}); tft_width = TFT_HEIGHT; tft_height = TFT_WIDTH; break; case TFT_ORIENTATION_LANDSCAPE_MIRROR: tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){TFT_MADCTL_LANDSCAPE_MIRROR}); tft_width = TFT_HEIGHT; tft_height = TFT_WIDTH; break; } } void tft_init_config() { tft_cmd(TFT_SOFTRESET, 1, NULL); sleep_ms(120); tft_cmd(TFT_SLEEPOUT, 0, NULL); sleep_ms(120); tft_cmd(TFT_COMMANDSET, 1, (uint8_t[1]){0xC3}); //enable part 1 tft_cmd(TFT_COMMANDSET, 1, (uint8_t[1]){0x96}); //enable part 2 //tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){0x88}); //MY,MX,MV,ML,BRG,MH,0,0(24), 0:RGB tft_set_orientation(TFT_ORIENTATION_PORTRAIT); tft_cmd(TFT_PIXELFORMAT, 1, (uint8_t[1]){0x05}); //0x05:RGB565, 0x06 RGB666 tft_cmd(TFT_DSIPLAY_INVER, 1, (uint8_t[1]){0x01}); // 1-dot //tft_cmd(TFT_DISPLAYFUNC, 3, (uint8_t[3]){0x0A, 0x82, 0x27}); // ILI9342 tft_cmd(TFT_DISPLAYFUNC, 3, (uint8_t[3]){0x80, 0x02, 0x3B}); // ST7796 tft_cmd(TFT_DISP_OUTPUT_CTRL_ADJUST, 8, (uint8_t[8]) {0x40, 0x8A, 0x00, 0x00, 0x29, //Source eqaulizing period time= 22.5 us 0x19, //Timing for "Gate start"=25 (Tclk) 0xA5, //Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON 0x33}); // ST7796 tft_cmd(TFT_POWERCONTROL2, 1, (uint8_t[1]){0x06}); // 0x05 :3.3V tft_cmd(TFT_POWERCONTROL3, 1, (uint8_t[1]){0xA7}); tft_cmd(TFT_VCOMCONTROL1, 1, (uint8_t[1]){0x18}); sleep_ms(120); tft_cmd(TFT_PGAMCOR, 14, (uint8_t[14]){ 0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b}); tft_cmd(TFT_NGAMCOR, 14, (uint8_t[14]){ 0xe0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2b, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}); sleep_ms(120); tft_cmd(TFT_COMMANDSET, 1, (uint8_t[1]){0x3C}); // disable part 1 tft_cmd(TFT_COMMANDSET, 1, (uint8_t[1]){0x69}); // disable part 2 tft_cmd(TFT_DISPLAYOFF, 0, NULL); sleep_ms(120); tft_cmd(TFT_DISPLAYON, 0, NULL); sleep_ms(500); } void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin) { tft_pio = pio; tft_sm = sm; #ifdef PICO_TFT_PARALLEL in_out_base_pin = din_base; set_base_pin = csx_dcx_sck_side_base_pin; tft_pio_cmd_init(tft_pio, tft_sm, in_out_base_pin, set_base_pin, 70000000); //pio freq #endif #ifdef PICO_TFT_SERIAL s_in_out_base_pin = din_base; sideset_base_pin = csx_dcx_sck_side_base_pin; tft_pio_cmd_init(tft_pio, tft_sm, s_in_out_base_pin, sideset_base_pin, 90000000/* 62.5M baud rate for SPI*/); //pio freq #endif tft_init_config(); }
- pico_tft.h
#ifndef _TFT_H_ #define _TFT_H_ #define TFT_WIDTH 320 #define TFT_HEIGHT 480 enum { TFT_ORIENTATION_PORTRAIT=0, TFT_ORIENTATION_LANDSCAPE, TFT_ORIENTATION_PORTRAIT_MIRROR, TFT_ORIENTATION_LANDSCAPE_MIRROR, }; #include "pico/stdlib.h" #include "hardware/pio.h" #include "pico_tft_lvgl.h" void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin); void tft_init_config(); void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color); void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); void tft_cmd(uint32_t cmd, uint32_t count, uint8_t *param); void tft_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param); uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B); void tft_lv_draw_bitmap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *bitmap); void tft_pio_cmd_init(PIO pio, uint sm, uint out_base, uint set_base, uint32_t freq); uint16_t tft_get_width(); uint16_t tft_get_height(); uint8_t tft_get_orientation(); void tft_set_orientation(uint8_t orientation); #endif
- xpt2046.c
#include "stdio.h" #include "stdlib.h" #include "xpt2046.h" #include "hardware/spi.h" #include "hardware/gpio.h" #include "pico/stdlib.h" #include "pico_tft.h" uint32_t xpt2046_event=0; uint8_t READ_X = 0xD0; uint8_t READ_Y = 0x90; #define XPT_WIDTH 320 #define XPT_HEIGHT 480 bool xpt2046_getXY(uint16_t *x, uint16_t *y) { uint8_t temp[2]; uint16_t raw_x, raw_y; uint16_t est_raw_x, est_raw_y; uint32_t avg_x = 0; uint32_t avg_y = 0; uint8_t nsamples = 0; uint8_t SAMPLES=10; gpio_put(XPT2046_CS, false); if(gpio_get(XPT2046_IRQ_GPIO)) return false; busy_wait_ms(10); //first pass SAMPLES=20; // get first average; for(uint8_t i = 0; i < SAMPLES; i++, nsamples++) { if(gpio_get(XPT2046_IRQ_GPIO)) { break; } spi_write_blocking(XPT2046_SPI, &READ_X, 1); spi_read_blocking(XPT2046_SPI, 0x00, temp, 2); raw_x = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1]; spi_write_blocking(XPT2046_SPI, &READ_Y, 1); spi_read_blocking(XPT2046_SPI, 0x00, temp, 2); raw_y = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1]; avg_x += raw_x; avg_y += raw_y; } if(nsamples < SAMPLES) return false; gpio_put(XPT2046_CS, true); raw_x = (avg_x / SAMPLES); raw_y = (avg_y / SAMPLES); if(raw_x < XPT2046_MIN_RAW_X || raw_x > XPT2046_MAX_RAW_X) return false; if(raw_y < XPT2046_MIN_RAW_Y || raw_y > XPT2046_MAX_RAW_Y) return false; uint16_t tx,ty; tx = (raw_x - XPT2046_MIN_RAW_X) * XPT_WIDTH / (XPT2046_MAX_RAW_X - XPT2046_MIN_RAW_X); ty = (raw_y - XPT2046_MIN_RAW_Y) * XPT_HEIGHT / (XPT2046_MAX_RAW_Y - XPT2046_MIN_RAW_Y); // adjust for TFT orientation uint8_t lot = tft_get_orientation(); switch (lot) { case TFT_ORIENTATION_PORTRAIT: *x=tx; *y=TFT_HEIGHT-ty; break; case TFT_ORIENTATION_LANDSCAPE: *x=TFT_HEIGHT-ty; *y=TFT_WIDTH-tx; break; case TFT_ORIENTATION_PORTRAIT_MIRROR: *x=TFT_WIDTH-tx; *y=ty; break; case TFT_ORIENTATION_LANDSCAPE_MIRROR: *x=ty; *y=tx; break; } return true; } bool xpt2046_TouchPressed() { return !gpio_get(XPT2046_IRQ_GPIO); } void xpt2046_init() { gpio_init(XPT2046_IRQ_GPIO); gpio_init(XPT2046_MISO); gpio_init(XPT2046_MOSI); gpio_init(XPT2046_CLK); gpio_init(XPT2046_CS); gpio_set_dir(XPT2046_CS, GPIO_OUT); gpio_set_dir(XPT2046_IRQ_GPIO, GPIO_OUT); gpio_set_function(XPT2046_CLK,GPIO_FUNC_SPI); gpio_set_function(XPT2046_CS,GPIO_FUNC_SIO); gpio_set_function(XPT2046_MOSI,GPIO_FUNC_SPI); gpio_set_function(XPT2046_MISO,GPIO_FUNC_SPI); //spi_init(XPT2046_SPI,250000); spi_init(XPT2046_SPI,3125000); //3125000 } extern lv_obj_t *mouse_cursor; void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data) { uint16_t x,y; if (xpt2046_TouchPressed()) { if (xpt2046_getXY(&x,&y)) { data->point.x = x; data->point.y = y; data->state = LV_INDEV_STATE_PRESSED; } else { data->state = LV_INDEV_STATE_RELEASED; } } else { data->state = LV_INDEV_STATE_RELEASED; } }
- xpt2046.h
#ifndef _XPT2046_H_ #define _XPT2046_H_ #include "pico/stdlib.h" #include "lvgl.h" #define XPT2046_IRQ_GPIO 11 #define XPT2046_MOSI 15 // 19 #define XPT2046_MISO 12 // 16 #define XPT2046_CS 13 // 17 #define XPT2046_CLK 14 // 18 #define XPT2046_SPI spi1 #define XPT2046_MIN_RAW_X 1350 //2000 #define XPT2046_MAX_RAW_X 31000 //30000 #define XPT2046_MIN_RAW_Y 2050 //1500 #define XPT2046_MAX_RAW_Y 31500 //29000 void xpt2046_init(); bool xpt2046_getXY(uint16_t *x, uint16_t *y); bool xpt2046_TouchPressed(); void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data); #endif
- hog_mouse.h
/* this file was modified from btstack example file:hog_mouse_demo.c*/ /* * Copyright (C) 2017 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at * contact@bluekitchen-gmbh.com * */ #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 "inttypes.h" static struct { int dx; int dy; int wheel; uint8_t buttons; } mouse_point; static btstack_packet_callback_registration_t hci_event_callback_registration; static btstack_packet_callback_registration_t l2cap_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 = 1; static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); const uint8_t adv_data[] = { // Flags general discoverable, BR/EDR not supported 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, // Name 0x11, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P','i','c','o',' ','W',' ', 'H', 'I', 'D', ' ', 'M', 'o', 'u', 's', 'e', // 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 - Mouse (Category 15, Sub-Category 2) 0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC2, 0x03, }; const uint8_t adv_data_len = sizeof(adv_data); // USB HID Specification 1.1, Appendix B.2 const uint8_t hid_descriptor_mouse_boot_mode[] = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x02, // USAGE (Mouse) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x01, // Report ID 1 0x09, 0x01, // USAGE (Pointer) 0xa1, 0x00, // COLLECTION (Physical) 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x03, // USAGE_MAXIMUM (Button 3) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x03, // REPORT_COUNT (3) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x05, // REPORT_SIZE (5) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x09, 0x38, // USAGE (WHEEL) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7f, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x03, // REPORT_COUNT (3) 0x81, 0x06, // INPUT (Data,Var,Rel) 0xc0, // END_COLLECTION 0xc0 // END_COLLECTION }; static void hog_mouse_setup(void){ // setup l2cap and l2cap_init(); // setup SM: Display only sm_init(); sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY); // sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING); sm_set_authentication_requirements(SM_AUTHREQ_BONDING); // setup ATT server att_server_init(profile_data, NULL, NULL); // setup battery service battery_service_server_init(battery); // setup device information service device_information_service_server_init(); // setup HID Device service hids_device_init(0, hid_descriptor_mouse_boot_mode, sizeof(hid_descriptor_mouse_boot_mode)); // 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); // register for events hci_event_callback_registration.callback = &packet_handler; hci_add_event_handler(&hci_event_callback_registration); // register for connection parameter updates l2cap_event_callback_registration.callback = &packet_handler; l2cap_add_event_handler(&l2cap_event_callback_registration); sm_event_callback_registration.callback = &packet_handler; sm_add_event_handler(&sm_event_callback_registration); hids_device_register_packet_handler(packet_handler); hci_power_control(HCI_POWER_ON); } // HID Report sending static void send_report(uint8_t buttons, int8_t dx, int8_t dy, int8_t wheel){ uint8_t report[] = { buttons, (uint8_t) dx, (uint8_t) dy, (uint8_t) wheel}; switch (protocol_mode){ case 0: hids_device_send_boot_mouse_input_report(con_handle, report, sizeof(report)); break; case 1: hids_device_send_input_report(con_handle, report, sizeof(report)); break; default: break; } } static void mousing_can_send_now(void){ send_report(mouse_point.buttons, mouse_point.dx, mouse_point.dy, mouse_point.wheel); // reset mouse_point.dx = 0; mouse_point.dy = 0; mouse_point.wheel=0; if (mouse_point.buttons){ mouse_point.buttons = 0; 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); uint16_t conn_interval; 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; break; case SM_EVENT_JUST_WORKS_REQUEST: 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 L2CAP_EVENT_CONNECTION_PARAMETER_UPDATE_RESPONSE: printf("L2CAP Connection Parameter Update Complete, response: %x\n", l2cap_event_connection_parameter_update_response_get_result(packet)); break; case HCI_EVENT_LE_META: switch (hci_event_le_meta_get_subevent_code(packet)) { case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: // print connection parameters (without using float operations) conn_interval = hci_subevent_le_connection_complete_get_conn_interval(packet); printf("LE Connection Complete:\n"); printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); printf("- Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet)); break; case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE: // print connection parameters (without using float operations) conn_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet); printf("LE Connection Update:\n"); printf("- Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); printf("- Connection Latency: %u\n", hci_subevent_le_connection_update_complete_get_conn_latency(packet)); break; default: break; } 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)); // request connection param update via L2CAP following Apple Bluetooth Design Guidelines // gap_request_connection_parameter_update(con_handle, 12, 12, 4, 100); // 15 ms, 4, 1s // directly update connection params via HCI following Apple Bluetooth Design Guidelines // gap_update_connection_parameters(con_handle, 12, 12, 4, 100); // 60-75 ms, 4, 1s 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_BOOT_MOUSE_INPUT_REPORT_ENABLE: con_handle = hids_subevent_boot_mouse_input_report_enable_get_con_handle(packet); printf("Boot Mouse Characteristic Subscribed %u\n", hids_subevent_boot_mouse_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: mousing_can_send_now(); break; default: break; } break; default: break; } }
- pico_lvgl_ble_mouse.gatt
PRIMARY_SERVICE, GAP_SERVICE CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Pico W HID Mouse" // add Battery Service #import <battery_service.gatt> // add Device ID Service #import <device_information_service.gatt> // add HID Service #import <hids.gatt> PRIMARY_SERVICE, GATT_SERVICE CHARACTERISTIC, GATT_DATABASE_HASH, READ,
- pico_lvbl_ble_mouse.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "picow_lvgl_ble_mouse.h" #include "pico_lvgl.h" #include "hog_mouse.h" // TFT PIO setting 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 // mouse pad object static lv_obj_t *mouse_cursor; static lv_obj_t *right_button; static lv_obj_t *left_button; static lv_obj_t *wheel_up; static lv_obj_t *wheel_down; void button_press_cb(lv_event_t *e) { lv_event_code_t code =lv_event_get_code(e); lv_obj_t* obj = lv_event_get_target(e); if (code == LV_EVENT_PRESSED) { if (obj == left_button) { mouse_point.buttons |= 1; // left button pressed } if (obj == right_button) { mouse_point.buttons |= 2; // right button pressed } hids_device_request_can_send_now_event(con_handle); } } void rect_pad_cb(lv_event_t *e) { lv_event_code_t code =lv_event_get_code(e); static uint16_t x, y; static int16_t ox, oy; if (code == LV_EVENT_PRESSED) { if (!xpt2046_getXY(&ox, &oy)) { ox=-1; oy=-1; } mouse_point.dx=0; mouse_point.dy=0; } if (code == LV_EVENT_PRESSING && xpt2046_getXY(&x, &y)) { lv_obj_set_x(mouse_cursor, x-lv_obj_get_style_pad_left(mouse_cursor,0)-15); lv_obj_set_y(mouse_cursor, y-lv_obj_get_style_pad_top(mouse_cursor,0)-15); if (ox > 0 && oy > 0) { mouse_point.dx = (x-ox)*2; mouse_point.dy = (y-oy)*2; ox=x; oy=y; hids_device_request_can_send_now_event(con_handle); } else { mouse_point.dx=0; mouse_point.dy=0; } } } void mouse_wheel_cb(lv_event_t *e) { lv_event_code_t code =lv_event_get_code(e); lv_obj_t* obj = lv_event_get_target(e); if (code == LV_EVENT_PRESSED || code == LV_EVENT_RELEASED) { mouse_point.wheel=0; hids_device_request_can_send_now_event(con_handle); } if (code == LV_EVENT_PRESSING) { if (obj == wheel_up) { mouse_point.wheel = 1; } if (obj == wheel_down) { mouse_point.wheel = -1; } hids_device_request_can_send_now_event(con_handle); } } void mouse_pad_init() { left_button = lv_btn_create(lv_scr_act()); right_button = lv_btn_create(lv_scr_act()); lv_obj_t* rect_pad = lv_obj_create(lv_scr_act()); lv_obj_t* wheel = lv_obj_create(lv_scr_act()); lv_obj_set_style_pad_all(wheel, 1, 0); wheel_up = lv_btn_create(wheel); wheel_down = lv_btn_create(wheel); lv_obj_set_size(left_button, lv_pct(42)-5,lv_pct(15)); lv_obj_set_size(right_button, lv_pct(42)-5,lv_pct(15)); lv_obj_set_size(wheel, lv_pct(10),lv_pct(15)); lv_obj_set_size(wheel_up, lv_pct(80),lv_pct(45)); lv_obj_t *label = lv_label_create(wheel_up); lv_label_set_text(label, LV_SYMBOL_UP); lv_obj_center(label); lv_obj_set_size(wheel_down, lv_pct(80),lv_pct(45)); label = lv_label_create(wheel_down); lv_obj_center(label); lv_label_set_text(label, LV_SYMBOL_DOWN); lv_obj_set_size(rect_pad, lv_pct(100), lv_pct(85)-4); lv_obj_add_event_cb(rect_pad, rect_pad_cb, LV_EVENT_ALL, NULL); lv_obj_set_scrollbar_mode(rect_pad, LV_SCROLLBAR_MODE_OFF); lv_obj_add_event_cb(left_button, button_press_cb, LV_EVENT_PRESSED, NULL); lv_obj_add_event_cb(right_button, button_press_cb, LV_EVENT_PRESSED, NULL); lv_obj_add_event_cb(wheel_up, mouse_wheel_cb, LV_EVENT_ALL, NULL); lv_obj_add_event_cb(wheel_down, mouse_wheel_cb, LV_EVENT_ALL, NULL); lv_obj_align(rect_pad, LV_ALIGN_TOP_MID, 0,0); lv_obj_align(wheel, LV_ALIGN_BOTTOM_MID, 0, -10); lv_obj_align_to(left_button, wheel, LV_ALIGN_OUT_LEFT_TOP, -10, 0); lv_obj_align_to(right_button, wheel, LV_ALIGN_OUT_RIGHT_TOP, 10, 0); static lv_style_t button_style; lv_style_init(&button_style); lv_style_set_bg_color(&button_style, lv_color_hex(0xaaffff)); lv_style_set_border_color(&button_style, lv_color_hex(0x0000ff)); lv_style_set_border_width(&button_style, 2); lv_style_set_shadow_color(&button_style, lv_color_black()); lv_style_set_shadow_ofs_x(&button_style, 2); lv_style_set_shadow_ofs_y(&button_style, 2); lv_obj_add_style(left_button, &button_style,0); lv_obj_add_style(right_button, &button_style,0); lv_obj_align(wheel_up, LV_ALIGN_TOP_MID, 0, 0); lv_obj_align(wheel_down, LV_ALIGN_BOTTOM_MID, 0, -2); mouse_cursor = lv_obj_create(rect_pad); lv_obj_set_style_bg_color(mouse_cursor, lv_color_hex(0xFF0000), 0); lv_obj_set_style_radius(mouse_cursor, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_color(mouse_cursor, lv_color_hex(0xE08080), 0); lv_obj_set_style_border_width(mouse_cursor, 8, 0); lv_obj_set_size(mouse_cursor,30,30); lv_obj_set_pos(mouse_cursor, lv_pct(50),lv_pct(50)); } int main() { stdio_init_all(); if (cyw43_arch_init()) { printf("cyw43_arch_init error\n"); return 0; } pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO); tft_set_orientation(TFT_ORIENTATION_LANDSCAPE); pico_lvgl_display_init(5); pico_lvgl_xpt2046_init(); mouse_pad_init(); hog_mouse_setup(); while (true) { lv_timer_handler(); sleep_ms(5); } }
- CMakeLists.txt
# == DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work == if(WIN32) set(USERHOME $ENV{USERPROFILE}) else() set(USERHOME $ENV{HOME}) endif() set(sdkVersion 2.0.0) set(toolchainVersion 13_2_Rel1) set(picotoolVersion 2.0.0) include(${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) # ==================================================================================== # Generated Cmake Pico project file cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) # == DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work == if(WIN32) set(USERHOME $ENV{USERPROFILE}) else() set(USERHOME $ENV{HOME}) endif() set(sdkVersion 2.0.0) set(toolchainVersion 13_2_Rel1) set(picotoolVersion 2.0.0) include(${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) # ==================================================================================== set(PICO_BOARD pico_w CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) project(picow_lvgl_ble_mouse 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_ble_mouse picow_lvgl_ble_mouse.c ) pico_set_program_name(picow_lvgl_ble_mouse "picow_lvgl_ble_mouse") pico_set_program_version(picow_lvgl_ble_mouse "0.1") # Modify the below lines to enable/disable output over UART/USB pico_enable_stdio_uart(picow_lvgl_ble_mouse 1) pico_enable_stdio_usb(picow_lvgl_ble_mouse 0) # Add the standard library to the build target_link_libraries(picow_lvgl_ble_mouse pico_stdlib pico_cyw43_arch_none pico_btstack_cyw43 pico_btstack_ble ) add_subdirectory(pico_lvgl) target_link_libraries(picow_lvgl_ble_mouse pico_lvgl ) pico_btstack_make_gatt_header(picow_lvgl_ble_mouse PRIVATE "${CMAKE_CURRENT_LIST_DIR}/picow_lvgl_ble_mouse.gatt") # Add the standard include files to the build target_include_directories(picow_lvgl_ble_mouse PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) pico_add_extra_outputs(picow_lvgl_ble_mouse)
沒有留言:
張貼留言