prettyprint

2024年9月12日 星期四

[Raspberry Pi Pico W] Bluetooth on-screen mouse using LVGL and Btstack libraries

 本篇文章介紹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)


  


沒有留言:

張貼留言