本篇文章介紹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)
沒有留言:
張貼留言