prettyprint

2024年8月16日 星期五

STM32 HAL|| 16 bit parallel LCD-TFT driver using FSMC interface for LVGL || DMA

 本文章介紹如何移植LVGL graphic library 到STM32上,另外說明有關LCD-TFT的driver使用STM32F4xx 的FSMC介面,STM32透過FSMC external memory access的方式驅動LCD-TFT。

一、使用硬體:

  • LCD-TFT


  • 開發版STM32_F4VE

二、STM32F4xx Bus matrix:


CPU or DMA透過Bus Matrix可直接存取FSMC。

三、STM407xx memory map:

使用FSMC bank1 其範圍為0x6000 0000~0x6fff ffff。

四、 FSMC write waveform and LCD-TFT write cycle sequence 的時序圖。
LCD-TFT D/CX:  Low時為command, High 實為GRAM data or commana的parameter data。相對於FSMC_A[25:0]的某一個address pin。

五、LCD-TFT Command&DATA address

本文章專案的FSMC設定如下圖:

LCD register select使用A18, 16 bits Data。因此A18為low時Address 為0x6000 0000代表command(D/CX low),。當A18為high 時Address 為0x6008 0000代表Data。
A[18]=HADDR[19]

六、移植LVGL library到STM32上:

  1. copy lvgl中的scr資料夾與lvgl.h和lv_conf.h(由lv_conf_template.h更改檔名)檔案到專案中,並將檔案加到include paths中。
  2. 設定定期呼叫lv_tick_inc(x)的timer,本專案設定為5ms。

  3. 在main loop:定期呼叫lv_timer_handler()。
  4. 呼叫lv_init()並設定display與input device的driver。
詳細程式碼附於文末。

七、成果展示:


八、程式碼:
  • lcd_fsmc.c
#include <lcd_fsmc.h>
#include <stdlib.h>
#include <stdio.h>
static uint8_t LCD_Orientation=0;
static uint16_t lcd_width = LCD_WIDTH; //default 240
static uint16_t lcd_height = LCD_HEIGHT; //default 320
// MADCTL register: MY,MX,MV,ML,BGR,MH,x,x
static uint8_t LCD_MADCTL_PORTRAIT = 0b01001000;
static uint8_t LCD_MADCTL_LANDSCAPE = 0b00101000;
static uint8_t LCD_MADCTL_PORTRAIT_MIRROR = 0b10001000;
static uint8_t LCD_MADCTL_LANDSCAPE_MIRROR = 0b11101000;
void lcd_cmd_write(unsigned char command)
{
LCD_CMD_WRITE(command);
}
void lcd_data_write(unsigned short data)
{
LCD_DATA_WRITE(data);
}
uint16_t lcd_get_width() {
return lcd_width;
}
uint16_t lcd_get_height() {
return lcd_height;
}
static void lcd_reset(void)
{
lcd_cmd_write(LCD_SOFTRESET);
HAL_Delay(50);
}
void lcd_set_window(unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1)
{
lcd_cmd_write(LCD_COLADDRSET);
lcd_data_write((x0 >> 8) & 0xFF);
lcd_data_write(x0 & 0xFF);
lcd_data_write((x1 >> 8) & 0xFF);
lcd_data_write(x1 & 0xFF);
lcd_cmd_write(LCD_PAGEADDRSET);
lcd_data_write((y0 >> 8) & 0xFF);
lcd_data_write(y0 & 0xFF);
lcd_data_write((y1 >> 8) & 0xFF);
lcd_data_write(y1 & 0xFF);
lcd_cmd_write(LCD_MEMORYWRITE);
}
void lcd_init(void)
{
lcd_reset();
lcd_cmd_write(LCD_DISPLAYOFF);
lcd_cmd_write(0xCF);
lcd_data_write(0x00);
lcd_data_write(0x83);
lcd_data_write(0x30);
lcd_cmd_write(0xED);
lcd_data_write(0x64);
lcd_data_write(0x03);
lcd_data_write(0x12);
lcd_data_write(0x81);
lcd_cmd_write(0xE8);
lcd_data_write(0x85);
lcd_data_write(0x01);
lcd_data_write(0x79);
lcd_cmd_write(0xCB);
lcd_data_write(0x39);
lcd_data_write(0x2C);
lcd_data_write(0x00);
lcd_data_write(0x34);
lcd_data_write(0x02);
lcd_cmd_write(0xF7);
lcd_data_write(0x20);
lcd_cmd_write(0xEA);
lcd_data_write(0x00);
lcd_data_write(0x00);
lcd_cmd_write(LCD_POWERCONTROL1);
lcd_data_write(0x26);
lcd_cmd_write(LCD_POWERCONTROL2);
lcd_data_write(0x11);
lcd_cmd_write(LCD_VCOMCONTROL1);
lcd_data_write(0x35);
lcd_data_write(0x3E);
lcd_cmd_write(LCD_VCOMCONTROL2);
lcd_data_write(0xBE);
lcd_cmd_write(LCD_MEMCONTROL);
lcd_data_write(LCD_MADCTL_PORTRAIT);
LCD_Orientation = LCD_ORIENTATION_PORTRAIT; // set TFT orientation default
lcd_cmd_write(LCD_PIXELFORMAT);
lcd_data_write(0x55);
lcd_cmd_write(LCD_FRAMECONTROLNORMAL);
lcd_data_write(0x00);
lcd_data_write(0x1B);
lcd_cmd_write(0xF2);
lcd_data_write(0x08);
lcd_cmd_write(LCD_GAMMASET);
lcd_data_write(0x01);
lcd_cmd_write(LCD_POSITIVEGAMMCORR);
lcd_data_write(0x1F);
lcd_data_write(0x1A);
lcd_data_write(0x18);
lcd_data_write(0x0A);
lcd_data_write(0x0F);
lcd_data_write(0x06);
lcd_data_write(0x45);
lcd_data_write(0x87);
lcd_data_write(0x32);
lcd_data_write(0x0A);
lcd_data_write(0x07);
lcd_data_write(0x02);
lcd_data_write(0x07);
lcd_data_write(0x05);
lcd_data_write(0x00);
lcd_cmd_write(LCD_NEGATIVEGAMMCORR);
lcd_data_write(0x00);
lcd_data_write(0x25);
lcd_data_write(0x27);
lcd_data_write(0x05);
lcd_data_write(0x10);
lcd_data_write(0x09);
lcd_data_write(0x3A);
lcd_data_write(0x78);
lcd_data_write(0x4D);
lcd_data_write(0x05);
lcd_data_write(0x18);
lcd_data_write(0x0D);
lcd_data_write(0x38);
lcd_data_write(0x3A);
lcd_data_write(0x1F);
lcd_cmd_write(LCD_COLADDRSET);
lcd_data_write(0x00);
lcd_data_write(0x00);
lcd_data_write(0x00);
lcd_data_write(0xEF);
lcd_cmd_write(LCD_PAGEADDRSET);
lcd_data_write(0x00);
lcd_data_write(0x00);
lcd_data_write(0x01);
lcd_data_write(0x3F);
lcd_cmd_write(LCD_ENTRYMODE);
lcd_data_write(0x07);
lcd_cmd_write(LCD_DISPLAYFUNC);
lcd_data_write(0x0A);
lcd_data_write(0x82);
lcd_data_write(0x27);
lcd_data_write(0x00);
lcd_cmd_write(LCD_SLEEPOUT);
HAL_Delay(100);
lcd_cmd_write(LCD_DISPLAYON);
HAL_Delay(100);
lcd_cmd_write(LCD_MEMORYWRITE);
}
void lcd_set_orientation(uint8_t Orientation)
{
LCD_Orientation = Orientation;
lcd_cmd_write(LCD_MEMCONTROL);
switch (LCD_Orientation)
{
case LCD_ORIENTATION_PORTRAIT:
lcd_data_write(LCD_MADCTL_PORTRAIT);
lcd_width = LCD_WIDTH;
lcd_height = LCD_HEIGHT;
break;
case LCD_ORIENTATION_PORTRAIT_MIRROR:
lcd_data_write(LCD_MADCTL_PORTRAIT_MIRROR);
lcd_width = LCD_WIDTH;
lcd_height = LCD_HEIGHT;
break;
case LCD_ORIENTATION_LANDSCAPE:
lcd_data_write(LCD_MADCTL_LANDSCAPE);
lcd_width = LCD_HEIGHT;
lcd_height = LCD_WIDTH;
break;
case LCD_ORIENTATION_LANDSCAPE_MIRROR:
lcd_data_write(LCD_MADCTL_LANDSCAPE_MIRROR);
lcd_width = LCD_HEIGHT;
lcd_height = LCD_WIDTH;
break;
default:
break;
}
lcd_cmd_write(LCD_MEMORYWRITE);
lcd_set_window(0, 0, lcd_width - 1, lcd_height - 1);
}
void lcd_backlight_off(void)
{
LCD_BL_OFF();
}
void lcd_backlight_on(void)
{
LCD_BL_ON();
}
void lcd_display_off(void)
{
lcd_cmd_write(LCD_DISPLAYOFF);
LCD_BL_OFF();
}
void lcd_display_on(void)
{
lcd_cmd_write(LCD_DISPLAYON);
LCD_BL_ON();
}
uint8_t lcd_get_orientation(void)
{
return LCD_Orientation;
}
void lcd_fill_RGB(uint16_t color, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
lcd_set_window(x, y, x+width - 1, y+height - 1);
int dimensions = width * height;
while(dimensions--)
{
lcd_data_write(color);
}
}
  • lcd_fsmc.h
#ifndef __LCD_FSMC_H_
#define __LCD_FSMC_H_
#include "main.h"
#include <stdbool.h>
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
#define LCD_BL_ON() HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET)
#define LCD_BL_OFF() HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET)
#define LCD_CMD_BASE ((uint32_t)0x60000000)
#define LCD_DATA_BASE ((uint32_t)0x60080000)
#define LCD_CMD_WRITE(command) *(volatile uint16_t *) (LCD_CMD_BASE) = (command)
#define LCD_DATA_WRITE(data) *(volatile uint16_t *) (LCD_DATA_BASE) = (data)
#define LCD_REGISTER_READ() *(volatile uint16_t *) (LCD_CMD_BASE)
#define LCD_DATA_READ() *(volatile uint16_t *) (LCD_DATA_BASE)
enum
{
LCD_ORIENTATION_PORTRAIT = 0,
LCD_ORIENTATION_LANDSCAPE = 1,
LCD_ORIENTATION_PORTRAIT_MIRROR = 2,
LCD_ORIENTATION_LANDSCAPE_MIRROR = 3
};
// LCD registers
#define LCD_NOP 0x00
#define LCD_SOFTRESET 0x01
#define LCD_READID 0x04
#define LCD_READSTATUS 0x09
#define LCD_READPOWERMODE 0x0A
#define LCD_READMADCTL 0x0B
#define LCD_READPIXELFORMAT 0x0C
#define LCD_READIMAGEFORMAT 0x0D
#define LCD_READSIGNALMODE 0x0E
#define LCD_READSELFDIAGNOSTIC 0x0F
#define LCD_SLEEPIN 0x10
#define LCD_SLEEPOUT 0x11
#define LCD_PARTIALMODE 0x12
#define LCD_NORMALDISP 0x13
#define LCD_INVERTOFF 0x20
#define LCD_INVERTON 0x21
#define LCD_GAMMASET 0x26
#define LCD_DISPLAYOFF 0x28
#define LCD_DISPLAYON 0x29
#define LCD_COLADDRSET 0x2A
#define LCD_PAGEADDRSET 0x2B
#define LCD_MEMORYWRITE 0x2C
#define LCD_COLORSET 0x2D
#define LCD_MEMORYREAD 0x2E
#define LCD_PARTIALAREA 0x30
#define LCD_VERTICALSCROLING 0x33
#define LCD_TEARINGEFFECTOFF 0x34
#define LCD_TEARINGEFFECTON 0x35
#define LCD_MEMCONTROL 0x36
#define LCD_VSCROLLSTARTADDRESS 0x37
#define LCD_IDLEMODEOFF 0x38
#define LCD_IDLEMODEON 0x39
#define LCD_PIXELFORMAT 0x3A
#define LCD_WRITEMEMCONTINUE 0x3C
#define LCD_READMEMCONTINUE 0x3E
#define LCD_SETSCANLINE 0x44
#define LCD_GETSCANLINE 0x45
#define LCD_WRITEBRIGHTNESS 0x51
#define LCD_READBRIGHTNESS 0x52
#define LCD_WRITECTRL 0x53
#define LCD_READCTRL 0x54
#define LCD_WRITECABC 0x55
#define LCD_READCABC 0x56
#define LCD_WRITECABCMIN 0x5E
#define LCD_READCABCMIN 0x5F
#define LCD_RGBSIGNALCONTROL 0xB0
#define LCD_FRAMECONTROLNORMAL 0xB1
#define LCD_FRAMECONTROLIDLE 0xB2
#define LCD_FRAMECONTROLPARTIAL 0xB3
#define LCD_INVERSIONCONTROL 0xB4
#define LCD_BLANKINGPORCHCONT 0xB5
#define LCD_DISPLAYFUNC 0xB6
#define LCD_ENTRYMODE 0xB7
#define LCD_BACKLIGHTCONTROL1 0xB8
#define LCD_BACKLIGHTCONTROL2 0xB9
#define LCD_BACKLIGHTCONTROL3 0xBA
#define LCD_BACKLIGHTCONTROL4 0xBB
#define LCD_BACKLIGHTCONTROL5 0xBC
#define LCD_BACKLIGHTCONTROL7 0xBE
#define LCD_BACKLIGHTCONTROL8 0xBF
#define LCD_POWERCONTROL1 0xC0
#define LCD_POWERCONTROL2 0xC1
#define LCD_VCOMCONTROL1 0xC5
#define LCD_VCOMCONTROL2 0xC7
#define LCD_NVMEMORYWRITE 0xD0
#define LCD_NVMEMORYKEY 0xD1
#define LCD_NVMEMORYSTATUSREAD 0xD2
#define LCD_READID4 0xD3
#define LCD_READID1 0xDA
#define LCD_READID2 0xDB
#define LCD_READID3 0xDC
#define LCD_POSITIVEGAMMCORR 0xE0
#define LCD_NEGATIVEGAMMCORR 0xE1
#define LCD_DIGITALGAMMCONTROL1 0xE2
#define LCD_DIGITALGAMMCONTROL2 0xE3
#define LCD_INTERFACECONTROL 0xF6
// LCD Registers
void lcd_init(void);
void lcd_fill_RGB(uint16_t color, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
void lcd_set_orientation(uint8_t Orientation);
void lcd_set_window(unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1);
void lcd_display_off(void);
void lcd_display_on(void);
void lcd_data_write(unsigned short data);
void lcd_cmd_write(unsigned char command);
void lcd_backlight_on();
void lcd_backlight_off();
uint16_t lcd_get_width();
uint16_t lcd_get_height();
uint8_t lcd_get_orientation();
#endif /* __LCD_FSMC_H_ */
  • lcd_lvgl.c
#include <lcd_lvgl.h>
/* Memory-to-memory DMA Handler */
extern DMA_HandleTypeDef hdma_memtomem_dma2_stream0;
extern uint8_t test_with_dma;
void tft_lvgl_draw_bitmap(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *bitmap)
{
uint32_t total_pixels = (x2-x1+1) * (y2-y1+1);
lcd_set_window(x1, y1, x2, y2);
// use memory-to-memory DMA
HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)bitmap, (LCD_DATA_BASE), total_pixels);
HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0, HAL_DMA_FULL_TRANSFER, 1000);
// if not use DMA translation
// for (int i=0; i < total_pixels; i++) {
// lcd_data_write(*(bitmap+i));
// }
}
void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
tft_lvgl_draw_bitmap(
(uint16_t)(area->x1),
(uint16_t)(area->y1),
(uint16_t)(area->x2),
(uint16_t)(area->y2), (uint16_t*)color_p
);
lv_disp_flush_ready(disp); /* Indicate you are ready with the flushing*/
}
void lvgl_init() {
lv_init();
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LCD_WIDTH * LCD_HEIGHT / 10]; /*Declare a buffer for 1/10 screen size*/
lv_disp_draw_buf_init(&draw_buf, buf1, NULL, LCD_WIDTH * LCD_HEIGHT / 10); /*Initialize the display buffer.*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.flush_cb = tft_lvgl_disp_flush; /*Set your driver function*/
disp_drv.draw_buf = &draw_buf; /*Assign the buffer to the display*/
disp_drv.hor_res = lcd_get_width(); /*Set the horizontal resolution of the display*/
disp_drv.ver_res = lcd_get_height(); /*Set the vertical resolution of the display*/
lv_disp_drv_register(&disp_drv); /*Finally register the driver*/
}
void lvgl_xpt2046_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
static uint16_t x, y;
if(XPT2046_TouchPressed()) {
XPT2046_TouchGetCoordinates(&x, &y);
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
void lvgl_xpt2046_touch_init() {
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); /*Basic initialization*/
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = lvgl_xpt2046_read_cb;
/*Register the driver in LVGL and save the created input device object*/
lv_indev_drv_register(&indev_drv);
}
  • lcd_lvgl.h
#ifndef __LCD_LVGL_H
#define __LCD_LVGL_H
#include <lcd_fsmc.h>
#include <lvgl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "XPT2046_touch.h"
void lvgl_init();
void lvgl_xpt2046_touch_init();
#endif /*__LCD_LVGL_H */
  • XPT2046_touch.c
#include <lcd_fsmc.h>
#include <stdio.h>
#include <stdlib.h>
#include "XPT2046_touch.h"
#define READ_X 0xD0
#define READ_Y 0x90
uint16_t cRawX_min = XPT2046_MIN_RAW_X;
uint16_t cRawX_max = XPT2046_MAX_RAW_X;
uint16_t cRawY_min = XPT2046_MIN_RAW_Y;
uint16_t cRawY_max = XPT2046_MAX_RAW_Y;
static void XPT2046_TouchSelect()
{
HAL_GPIO_WritePin(XPT2046_CS_GPIO_Port, XPT2046_CS_Pin, GPIO_PIN_RESET);
}
void XPT2046_TouchUnselect()
{
HAL_GPIO_WritePin(XPT2046_CS_GPIO_Port, XPT2046_CS_Pin, GPIO_PIN_SET);
}
bool XPT2046_TouchPressed()
{
return HAL_GPIO_ReadPin(XPT2046_IRQ_GPIO_Port, XPT2046_IRQ_Pin) == GPIO_PIN_RESET;
}
bool XPT2046_TouchGetCoordinates(uint16_t* x, uint16_t* y)
{
bool ret_value=false;
uint16_t tx,ty;
uint32_t raw_x;
uint32_t raw_y;
if (XPT2046_TouchGetRawCoordinates(&raw_x, &raw_y))
{
if(raw_x < cRawX_min) raw_x = cRawX_min;
if(raw_x > cRawX_max) raw_x = cRawX_max;
if(raw_y < cRawY_min) raw_y = cRawY_min;
if(raw_y > cRawY_max) raw_y = cRawY_max;
tx = (raw_x - cRawX_min) * XPT2046_SCALE_X / (cRawX_max - cRawX_min);
ty = (raw_y - cRawY_min) * XPT2046_SCALE_Y / (cRawY_max - cRawY_min);
uint8_t lot = lcd_get_orientation();
switch (lot)
{
case LCD_ORIENTATION_PORTRAIT:
*x=tx;
*y=ty;
break;
case LCD_ORIENTATION_LANDSCAPE:
*x=ty;
*y=LCD_WIDTH-tx;
break;
case LCD_ORIENTATION_PORTRAIT_MIRROR:
*x=LCD_WIDTH-tx;
*y=LCD_HEIGHT-ty;
break;
case LCD_ORIENTATION_LANDSCAPE_MIRROR:
*x=LCD_HEIGHT-ty;
*y=tx;
break;
}
ret_value =true;
}
return ret_value;
}
bool XPT2046_TouchGetRawCoordinates(uint32_t* raw_x, uint32_t* raw_y)
{
static const uint8_t cmd_read_x[] = { READ_X };
static const uint8_t cmd_read_y[] = { READ_Y };
static const uint8_t zeroes_tx[] = { 0x00, 0x00 };
static const uint8_t SAMPLES=16;
//if (!XPT2046_TouchPressed()) return false;
//HAL_Delay(10);
XPT2046_TouchSelect();
uint32_t avg_x = 0;
uint32_t avg_y = 0;
uint8_t nsamples = 0;
for(uint8_t i = 0; i < SAMPLES; i++)
{
if(!XPT2046_TouchPressed()) {
break;
}
nsamples++;
HAL_SPI_Transmit(&XPT2046_SPI_PORT, (uint8_t*)cmd_read_y, sizeof(cmd_read_y), HAL_MAX_DELAY);
uint8_t y_raw[2];
HAL_SPI_TransmitReceive(&XPT2046_SPI_PORT, (uint8_t*)zeroes_tx, y_raw, sizeof(y_raw), HAL_MAX_DELAY);
HAL_SPI_Transmit(&XPT2046_SPI_PORT, (uint8_t*)cmd_read_x, sizeof(cmd_read_x), HAL_MAX_DELAY);
uint8_t x_raw[2];
HAL_SPI_TransmitReceive(&XPT2046_SPI_PORT, (uint8_t*)zeroes_tx, x_raw, sizeof(x_raw), HAL_MAX_DELAY);
avg_x += (((uint16_t)x_raw[0]) << 8) | ((uint16_t)x_raw[1]);
avg_y += (((uint16_t)y_raw[0]) << 8) | ((uint16_t)y_raw[1]);
}
XPT2046_TouchUnselect();
if(nsamples < SAMPLES)
return false;
*raw_x = (avg_x / SAMPLES);
*raw_y = (avg_y / SAMPLES);
return true;
}
bool XPT2046_TouchCalibration()
{
uint32_t x0=0,y0=0,x1=0,y1=0,x2=0,y2=0,x3=0,y3=0;
bool correct=true;
uint32_t width, height;
uint8_t lot = lcd_get_orientation();
lcd_set_orientation(LCD_ORIENTATION_PORTRAIT);
width = lcd_get_width();
height = lcd_get_height();
lcd_fill_RGB(0x0000, 0, 0, width-1, height-1);
lcd_fill_RGB(0xffff, 0, 0, 6,6);
lcd_set_window(20,100 ,20 ,100); // set LCD cursor to (20,100)
while(!XPT2046_TouchPressed()) ;
if (!XPT2046_TouchGetRawCoordinates(&x0, &y0))
{
lcd_set_orientation(lot);
return false;
}
lcd_fill_RGB(0x0000, 0, 0, width-1, height-1);
lcd_fill_RGB(0xffff, 0, height-7, 6, 6);
while(XPT2046_TouchPressed());
HAL_Delay(1);
lcd_set_window(20, 100, 20, 100);
while(!XPT2046_TouchPressed());
if(!XPT2046_TouchGetRawCoordinates(&x1, &y1))
{
lcd_set_orientation(lot);
return false;
}
lcd_fill_RGB(0x0000, 0, 0, width-1, height-1);
lcd_fill_RGB(0xffff,width-7, height-7, 6, 6);
while(XPT2046_TouchPressed());
HAL_Delay(1);
lcd_set_window(20, 100, 20, 100);
while(!XPT2046_TouchPressed());
if (!XPT2046_TouchGetRawCoordinates(&x2, &y2))
{
lcd_set_orientation(lot);
return false;
}
lcd_fill_RGB(0x0000, 0, 0, width-1, height-1);
lcd_fill_RGB(0xffff, width-7, 0, 6, 6);
while(XPT2046_TouchPressed());
HAL_Delay(1);
lcd_set_window(20, 100, 20, 100);
while(!XPT2046_TouchPressed());
if (!XPT2046_TouchGetRawCoordinates(&x3, &y3))
{
lcd_set_orientation(lot);
return false;
}
while(XPT2046_TouchPressed());
if (abs(x0-x1) > XTP2046_CALI_DIFF) correct = false;
if (abs(x2-x3) > XTP2046_CALI_DIFF) correct = false;
if (abs(y1-y2) > XTP2046_CALI_DIFF) correct = false;
if (abs(y0-y3) > XTP2046_CALI_DIFF) correct = false;
if (correct) {
cRawX_min = (x0+x1)/2;
cRawX_max = (x2+x3)/2;
cRawY_min = (y0+y3)/2;
cRawY_max = (y1+y2)/2;
}
lcd_fill_RGB(0x0000, 0, 0, width-1, height-1);
lcd_set_window(20, 100, 20, 100);
lcd_set_orientation(lot);
return correct;
}
  • XPT2046_touch.h
#ifndef XPT2046_TOUCH_H_
#define XPT2046_TOUCH_H_
#include "main.h"
#include <stdbool.h>
/*** Redefine if necessary ***/
// Warning! Use SPI bus with < 2.5 Mbit speed, better ~650 Kbit to be save.
#define XPT2046_SPI_PORT hspi2
extern SPI_HandleTypeDef XPT2046_SPI_PORT;
#define XPT2046_IRQ_Pin T_IRQ_Pin
#define XPT2046_IRQ_GPIO_Port T_IRQ_GPIO_Port
#define XPT2046_CS_Pin T_CS_Pin
#define XPT2046_CS_GPIO_Port T_CS_GPIO_Port
// change depending on screen orientation
#define XPT2046_SCALE_X 240
#define XPT2046_SCALE_Y 320
#define XPT2046_MIN_RAW_X 1860
#define XPT2046_MAX_RAW_X 29650
#define XPT2046_MIN_RAW_Y 1830
#define XPT2046_MAX_RAW_Y 29350
//#define XPT2046_MIN_RAW_X 2000
//#define XPT2046_MAX_RAW_X 30000
//#define XPT2046_MIN_RAW_Y 1500
//#define XPT2046_MAX_RAW_Y 29000
#define XTP2046_CALI_DIFF 2500
// call before initializing any SPI devices
void XPT2046_TouchUnselect(void);
bool XPT2046_TouchPressed(void);
bool XPT2046_TouchGetCoordinates(uint16_t* x, uint16_t* y);
bool XPT2046_TouchGetRawCoordinates(uint32_t* raw_, uint32_t* raw_y);
bool XPT2046_TouchCalibration(void);
#endif /* XPT2046_TOUCH_H_ */

2024年8月3日 星期六

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

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

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

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

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




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

  • 按鍵排例方式


  • 每個按鍵控制模式

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

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

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

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

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

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

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

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

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

成果影片:



程式碼:


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

CMakeLists.txt
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")
set(PICO_BOARD pico_w CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()
project(picow_lvgl_hog_keyboard C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(picow_lvgl_hog_keyboard
picow_lvgl_hog_keyboard.c
lvgl_keyboard.c
zh_TW_kb.c
shortcuts.c
)
pico_set_program_name(picow_lvgl_hog_keyboard "picow_lvgl_bt_kb")
pico_set_program_version(picow_lvgl_hog_keyboard "0.1")
pico_enable_stdio_uart(picow_lvgl_hog_keyboard 1)
pico_enable_stdio_usb(picow_lvgl_hog_keyboard 0)
# Add the standard library to the build
target_link_libraries(picow_lvgl_hog_keyboard
pico_stdlib)
# Add the standard include files to the build
target_include_directories(picow_lvgl_hog_keyboard PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)
# Add any user requested libraries
target_link_libraries(picow_lvgl_hog_keyboard
hardware_spi
hardware_dma
hardware_pio
pico_util
pico_cyw43_arch_none
pico_btstack_cyw43
pico_btstack_ble
)
add_subdirectory(pico_lvgl)
target_link_libraries(picow_lvgl_hog_keyboard
pico_lvgl
)
pico_btstack_make_gatt_header(picow_lvgl_hog_keyboard PRIVATE "${CMAKE_CURRENT_LIST_DIR}/picow_lvgl_hog_keyboard.gatt")
pico_add_extra_outputs(picow_lvgl_hog_keyboard)
lvgl_keyboard.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "pico_lvgl.h"
#include "usb_hid_scan_code.h"
extern const lv_font_t jf_font;
uint8_t lvgl_kb_left[4] = {239, 129, 147,0};
uint8_t lvgl_kb_right[4] = {239, 129, 148,0};
uint8_t lvgl_kb_ok[4] = {239, 128, 140,0};
uint8_t lvgl_kb_enter[4] = {239, 162, 162,0};
uint8_t lvgl_kb_bs[4] = {239, 149, 154,0};
uint8_t lvgl_kb_kb[4] = {239, 132, 156,0};
void set_zh_TW_keyboard(lv_obj_t *kb);
void shortcuts_init(lv_obj_t* parent);
void key_input(char character);
void send_hid_spec_code(uint8_t modifier, uint8_t keycode);
static uint8_t phonetic_selected=0;
void toggle_sel_key(lv_obj_t* kb) {
if (phonetic_selected==0) {
phonetic_selected = 1;
send_hid_spec_code(0, KEY_DOWN);
for(int i=0; i < 10; i++) {
lv_btnmatrix_clear_btn_ctrl(kb,i,LV_BTNMATRIX_CTRL_HIDDEN);
lv_btnmatrix_set_btn_ctrl(kb,i, LV_KEYBOARD_CTRL_BTN_FLAGS|1);
}
}
else{
if (phonetic_selected == 1) {
send_hid_spec_code(0, KEY_UP);
}
for(int i=0; i < 10; i++) {
lv_btnmatrix_clear_btn_ctrl(kb,i,LV_KEYBOARD_CTRL_BTN_FLAGS|1);
lv_btnmatrix_set_btn_ctrl(kb,i, LV_BTNMATRIX_CTRL_HIDDEN);
}
phonetic_selected=0;
}
}
static bool lookup_phonetic_keycode(lv_obj_t *kb, uint8_t* key, uint8_t * keycode){
uint16_t size = 57;//sizeof(keyboard_phonetic);
int i;
for (i=0;i<size;i++){
if (strcmp(keyboard_phonetic[i], key) !=0) continue;
*keycode = i;
return true;
}
if (key[0] >= '0' && key[0] <= '9'){
key_input(key[0]);
phonetic_selected++;
toggle_sel_key(kb);
}
return false;
}
uint8_t lvgl_keyboard_mode;
void lvgl_keyboard_cb(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t* obj = lv_event_get_target(e);
lv_obj_t * kb = lv_event_get_user_data(e);
char key[5];
if (code == LV_EVENT_CLICKED) {
strcpy(key, lv_keyboard_get_btn_text(kb,lv_keyboard_get_selected_btn(kb)));
if (strcmp(key, "ABC")==0) {
lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_UPPER;
return;
}
if (strcmp(key, "abc") == 0) {
lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_LOWER;
return;
}
if (lv_keyboard_get_mode(kb) != LV_KEYBOARD_MODE_USER_1) {
if ((lvgl_keyboard_mode == LV_KEYBOARD_MODE_TEXT_LOWER || lvgl_keyboard_mode == LV_KEYBOARD_MODE_TEXT_UPPER ) &&
key[0] == '1') {
lvgl_keyboard_mode = LV_KEYBOARD_MODE_SPECIAL;
return;
}
if (strlen(key) == 1) {
key_input(key[0]);
return;
}
} else {
static uint8_t keycode;
if (lookup_phonetic_keycode(kb, key, &keycode)) {
send_hid_spec_code(0, keycode);
return;
}
}
if (strcmp(key, lvgl_kb_enter)==0) {
key_input(CHAR_RETURN);
return;
}
if (strcmp(key, lvgl_kb_right)==0 || strcmp(key, "〉") == 0) {
send_hid_spec_code(0, KEY_RIGHT);
return;
}
if (strcmp(key, lvgl_kb_left)==0 || strcmp(key, "〈") == 0) {
send_hid_spec_code(0, KEY_LEFT);
return;
}
if (strcmp(key, lvgl_kb_bs)==0 || strcmp(key, "←")==0) {
key_input(CHAR_BACKSPACE);
return;
}
if (strcmp(key, "︿") == 0) {
send_hid_spec_code(0, KEY_UP);
return;
}
if (strcmp(key, "﹀") == 0) {
send_hid_spec_code(0, KEY_DOWN);
return;
}
if (strcmp(key, ",") == 0) {
//for Windows
//send_hid_spec_code(0, KEY_GRAVE);send_hid_spec_code(0, KEY_COMMA);
send_hid_spec_code(KEY_MOD_LSHIFT, KEY_COMMA);
return;
}
if (strcmp(key, "。") == 0) {
///for Windows
//send_hid_spec_code(0, KEY_GRAVE);send_hid_spec_code(0, KEY_DOT);
send_hid_spec_code(KEY_MOD_LSHIFT, KEY_DOT);
return;
}
if (strcmp(key, lvgl_kb_ok)==0) {
key_input(CHAR_RETURN);
return;
}
if (strcmp(key, lvgl_kb_kb)==0){
lv_obj_set_style_text_font(kb, &jf_font, LV_PART_ITEMS);
lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_USER_1);
send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;
return;
}
if (strcmp(key, "英")==0){
lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_TEXT_LOWER);
send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;
return;
}
if (strcmp(key, "數")==0){
lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_SPECIAL);
send_hid_spec_code(KEY_MOD_LCTRL, 0x2c); //0x2c space key scan code;
return;
}
if (strcmp(key, "選字")==0){
toggle_sel_key(kb);
return;
}
}
}
void lvgl_keyboard_init(void)
{
/*Create a keyboard to use it with an of the text areas*/
lv_obj_t * lvgl_keyboard = lv_keyboard_create(lv_scr_act());
lv_obj_t *shortcuts_frame = lv_obj_create(lv_scr_act());
shortcuts_init(shortcuts_frame);
lv_obj_set_size(shortcuts_frame, lv_pct(100), lv_pct(32));
lv_obj_set_style_pad_all(shortcuts_frame, 3,0);
lv_obj_set_style_bg_color(shortcuts_frame, lv_palette_main(LV_PALETTE_LIGHT_BLUE),0);
set_zh_TW_keyboard(lvgl_keyboard);
lvgl_keyboard_mode = LV_KEYBOARD_MODE_TEXT_LOWER;
lv_obj_set_size(lvgl_keyboard, lv_pct(100), lv_pct(67));
lv_obj_add_event_cb(lvgl_keyboard, lvgl_keyboard_cb, LV_EVENT_ALL, lvgl_keyboard);
}
pico_lvgl_hog_keyboard.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "btstack.h"
#include "ble/gatt-service/battery_service_server.h"
#include "ble/gatt-service/device_information_service_server.h"
#include "ble/gatt-service/hids_device.h"
#include "picow_lvgl_hog_keyboard.h"
#include "inttypes.h"
#include "pico_lvgl.h"
#include "usb_hid_scan_code.h"
extern const lv_font_t jf_font;
PIO TFT_PIO = pio0;
#define TFT_SM 0
#define TFT_SDI_GPIO 9
#define TFT_CSX_DCX_SCK_GPIO 6 // CSX=8, DCX=7, SCK=6, SIDE_SET
void lvgl_keyboard_init();
static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_packet_callback_registration_t sm_event_callback_registration;
static uint8_t battery = 100;
static hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID;
static uint8_t protocol_mode = HID_PROTOCOL_MODE_REPORT;
const uint8_t adv_data[] = {
// Flags general discoverable, BR/EDR not supported
0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
// Name
0x0d, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'H', 'I', 'D', ' ', 'K', 'e', 'y', 'b', 'o', 'a', 'r', 'd',
//0x0d
// 16-bit Service UUIDs
0x03, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE & 0xff, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE >> 8,
// Appearance HID - Keyboard (Category 15, Sub-Category 1)
0x03, BLUETOOTH_DATA_TYPE_APPEARANCE, 0xC1, 0x03,
};
const uint8_t adv_data_len = sizeof(adv_data);
// Buffer for 30 characters
static uint8_t key_input_storage[30];
static btstack_ring_buffer_t key_input_buffer;
// HID Keyboard lookup
static int lookup_keycode(uint8_t character, const uint8_t * table, int size, uint8_t * keycode){
int i;
for (i=0;i<size;i++){
if (table[i] != character) continue;
*keycode = i;
return 1;
}
return 0;
}
static int keycode_and_modifer_us_for_character(uint8_t character, uint8_t * keycode, uint8_t * modifier){
int found;
found = lookup_keycode(character, keytable_scan_codes, sizeof(keytable_scan_codes), keycode);
if (found) {
*modifier = 0; // none
return 1;
}
found = lookup_keycode(character, keytable_scan_codes_shift, sizeof(keytable_scan_codes_shift), keycode);
if (found) {
*modifier = 2; // shift
return 1;
}
return 0;
}
// HID Report sending
static void send_report(int modifier, int keycode){
uint8_t report[] = { modifier, 0, keycode, 0, 0, 0, 0, 0};
switch (protocol_mode){
case 0:
hids_device_send_boot_keyboard_input_report(con_handle, report, sizeof(report));
break;
case 1:
hids_device_send_input_report(con_handle, report, sizeof(report));
break;
default:
break;
}
}
static enum {
W4_INPUT,
W4_CAN_SEND_FROM_BUFFER,
W4_CAN_SEND_KEY_UP,
} state;
static void typing_can_send_now(void){
switch (state){
case W4_CAN_SEND_FROM_BUFFER:
while (1){
uint8_t c;
uint32_t num_bytes_read;
btstack_ring_buffer_read(&key_input_buffer, &c, 1, &num_bytes_read);
if (num_bytes_read == 0){
state = W4_INPUT;
break;
}
uint8_t modifier;
uint8_t keycode;
int found = keycode_and_modifer_us_for_character(c, &keycode, &modifier);
if (!found) continue;
printf("sending: %c\n", c);
send_report(modifier, keycode);
state = W4_CAN_SEND_KEY_UP;
hids_device_request_can_send_now_event(con_handle);
break;
}
break;
case W4_CAN_SEND_KEY_UP:
send_report(0, 0);
if (btstack_ring_buffer_bytes_available(&key_input_buffer)){
state = W4_CAN_SEND_FROM_BUFFER;
hids_device_request_can_send_now_event(con_handle);
} else {
state = W4_INPUT;
}
break;
default:
break;
}
}
void send_hid_spec_code(uint8_t modifier, uint8_t keycode) {
send_report(modifier, keycode);
state = W4_CAN_SEND_KEY_UP;
hids_device_request_can_send_now_event(con_handle);
}
void key_input(char character){
uint8_t c = character;
btstack_ring_buffer_write(&key_input_buffer, &c, 1);
// start sending
if (state == W4_INPUT && con_handle != HCI_CON_HANDLE_INVALID){
state = W4_CAN_SEND_FROM_BUFFER;
hids_device_request_can_send_now_event(con_handle);
}
}
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
if (packet_type != HCI_EVENT_PACKET) return;
switch (hci_event_packet_get_type(packet)) {
case HCI_EVENT_DISCONNECTION_COMPLETE:
con_handle = HCI_CON_HANDLE_INVALID;
printf("Disconnected\n");
break;
case SM_EVENT_JUST_WORKS_REQUEST:
printf("Just Works requested\n");
sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
break;
case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
break;
case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
break;
case HCI_EVENT_HIDS_META:
switch (hci_event_hids_meta_get_subevent_code(packet)){
case HIDS_SUBEVENT_INPUT_REPORT_ENABLE:
con_handle = hids_subevent_input_report_enable_get_con_handle(packet);
printf("Report Characteristic Subscribed %u\n", hids_subevent_input_report_enable_get_enable(packet));
break;
case HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE:
con_handle = hids_subevent_boot_keyboard_input_report_enable_get_con_handle(packet);
printf("Boot Keyboard Characteristic Subscribed %u\n", hids_subevent_boot_keyboard_input_report_enable_get_enable(packet));
break;
case HIDS_SUBEVENT_PROTOCOL_MODE:
protocol_mode = hids_subevent_protocol_mode_get_protocol_mode(packet);
printf("Protocol Mode: %s mode\n", hids_subevent_protocol_mode_get_protocol_mode(packet) ? "Report" : "Boot");
break;
case HIDS_SUBEVENT_CAN_SEND_NOW:
typing_can_send_now();
break;
default:
break;
}
break;
default:
break;
}
}
void hog_keyboard_init() {
//1. initialize ring buffer for key input
btstack_ring_buffer_init(&key_input_buffer, key_input_storage, sizeof(key_input_storage));
//2. l2cap initialize
l2cap_init();
//3. setup SM: Display only
sm_init();
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);
//4. setup ATT server
att_server_init(profile_data, NULL, NULL);
//5. setup battery service
battery_service_server_init(battery);
//6. setup device information service
device_information_service_server_init();
//7. setup HID Device service
hids_device_init(0, hid_descriptor_keyboard_boot_mode, sizeof(hid_descriptor_keyboard_boot_mode));
//8. setup advertisements
uint16_t adv_int_min = 0x0030;
uint16_t adv_int_max = 0x0030;
uint8_t adv_type = 0;
bd_addr_t null_addr;
memset(null_addr, 0, 6);
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data);
gap_advertisements_enable(1);
//9. register for HCI events
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
//10. register for SM events
sm_event_callback_registration.callback = &packet_handler;
sm_add_event_handler(&sm_event_callback_registration);
//11. register for HIDS
hids_device_register_packet_handler(packet_handler);
hci_power_control(HCI_POWER_ON);
}
int main()
{
stdio_init_all();
if (cyw43_arch_init()) {
printf("cyw43_arch_init error\n");
return 0;
}
//0. initialize keyboard & lvgl TFT
pico_lvgl_tft_init(TFT_PIO, TFT_SM, TFT_SDI_GPIO, TFT_CSX_DCX_SCK_GPIO);
pico_lvgl_display_init(5);
pico_lvgl_xpt2046_init();
//pico_lvgl_encoder_init(false);
lvgl_keyboard_init();
hog_keyboard_init();
uint8_t c;
while(1) {
lv_timer_handler();
sleep_ms(1);
if (con_handle == HCI_CON_HANDLE_INVALID) continue;
}
return 0;
}
pico_lvgl_hog_keyboard.gatt
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "PicoW BLE HID Keyboard"
CHARACTERISTIC, GATT_DATABASE_HASH, READ,
// add Battery Service
#import <battery_service.gatt>
// add Device ID Service
#import <device_information_service.gatt>
// add HID Service
#import <hids.gatt>
shortcuts.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "pico_lvgl.h"
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_A 0x04 // Keyboard a and A
#define KEY_B 0x05 // Keyboard b and B
#define KEY_C 0x06 // Keyboard c and C
#define KEY_D 0x07 // Keyboard d and D
#define KEY_E 0x08 // Keyboard e and E
#define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT 0x04
#define KEY_MOD_LMETA 0x08
#define KEY_MOD_RCTRL 0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT 0x40
#define KEY_MOD_RMETA 0x80
#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
#define KEY_TAB 0x2b // Keyboard Tab
#define KEY_SPACE 0x2c // Keyboard Spacebar
void send_hid_spec_code(uint8_t modifier, uint8_t keycode);
static const char* shortcuts_map[] = {
LV_SYMBOL_HOME "Home", "<Back", LV_SYMBOL_OK "Select", LV_SYMBOL_PREV "Prev",LV_SYMBOL_NEXT "Next", "\n",
"ESC", "Tab", LV_SYMBOL_CLOSE "Close", "Ctrl+SP",""
};
static const uint16_t KB_BTN = LV_KEYBOARD_CTRL_BTN_FLAGS;
static const uint16_t KB_HIDDEN = LV_BTNMATRIX_CTRL_HIDDEN;
static const uint16_t shortcuts_ctl_map[] = {
KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
KB_BTN,KB_BTN,KB_BTN,KB_BTN|2
};
static void shortcuts_btnmatrix_cb(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t* obj = lv_event_get_user_data(e);
uint16_t select_btn;
if (code == LV_EVENT_CLICKED) {
select_btn = lv_btnmatrix_get_selected_btn(obj);
switch(select_btn) {
case 0:
send_hid_spec_code(0, KEY_F1);
break;
case 1:
send_hid_spec_code(0, KEY_F2);
break;
case 2:
send_hid_spec_code(0, KEY_F3);
break;
case 3:
send_hid_spec_code(0, KEY_F4);
break;
case 4:
send_hid_spec_code(0, KEY_F5);
break;
case 5:
send_hid_spec_code(0, KEY_ESC);
break;
case 6:
send_hid_spec_code(0, KEY_TAB);
break;
case 7:
send_hid_spec_code(KEY_MOD_LALT, KEY_F4);
break;
case 8:
send_hid_spec_code(KEY_MOD_LCTRL, KEY_SPACE);
break;
}
}
}
void shortcuts_init(lv_obj_t* parent) {
lv_obj_t *shortcuts = lv_btnmatrix_create(parent);
lv_btnmatrix_set_map(shortcuts, shortcuts_map);
lv_btnmatrix_set_btn_ctrl_all(shortcuts, KB_BTN);
lv_obj_set_size(shortcuts, lv_pct(100), lv_pct(100));
lv_obj_set_style_pad_all(shortcuts, 0,0);
lv_obj_add_event_cb(shortcuts, shortcuts_btnmatrix_cb, LV_EVENT_ALL, shortcuts);
}
usb_hid_scan_code.h
#ifndef __USB_HID_SCAN_CODE_H__
#define __USB_HID_SCAN_CODE_H__
#include "stdio.h"
#include "pico/stdlib.h"
/**
* Modifier masks - used for the first byte in the HID report.
* NOTE: The second byte in the report is reserved, 0x00
*/
#define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT 0x04
#define KEY_MOD_LMETA 0x08
#define KEY_MOD_RCTRL 0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT 0x40
#define KEY_MOD_RMETA 0x80
#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF 0x01 // Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
// 0x02 // Keyboard POST Fail
// 0x03 // Keyboard Error Undefined
#define SPECIAL_KEY 0xff
#define CHAR_RETURN '\n'
#define CHAR_ESCAPE 27
#define CHAR_TAB '\t'
#define CHAR_BACKSPACE 0x7f
#define KEY_DELETE 0x4c // Keyboard Delete Forward
#define KEY_RIGHT 0x4f // Keyboard Right Arrow
#define KEY_LEFT 0x50 // Keyboard Left Arrow
#define KEY_DOWN 0x51 // Keyboard Down Arrow
#define KEY_UP 0x52 // Keyboard Up Arrow
// special keys
#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_F6 0x3f // Keyboard F6
#define KEY_F7 0x40 // Keyboard F7
#define KEY_F8 0x41 // Keyboard F8
#define KEY_F9 0x42 // Keyboard F9
#define KEY_F10 0x43 // Keyboard F10
#define KEY_F11 0x44 // Keyboard F11
#define KEY_F12 0x45 // Keyboard F12
#define KEY_GRAVE 0x35 // Keyboard ` and ~
#define KEY_COMMA 0x36 // Keyboard , and <
#define KEY_DOT 0x37 // Keyboard . and >
static const uint8_t keytable_scan_codes[] = {
SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,'a','b','c','d','e','f','g','h','i','j','k','l', //0x00~0x0F
'm','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2', //0x10~0x1F
'3','4','5','6','7','8','9','0',CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB,' ','-','=','[', //0x20~0x2F
']','\\',SPECIAL_KEY,';','\'','`',',','.','/',
};
static const uint8_t keytable_scan_codes_shift[] = {
SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,SPECIAL_KEY,'A','B','C','D','E','F','G','H','I','J','K','L', //0x00~0x0F
'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','!','@', //0x10~0x1F
'#','$','%','^','&','*','(',')',CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB,' ','_','+','{', //0x20~0x2F
'}','|',SPECIAL_KEY,':','"','~','<','>','?',
};
static const char ILLEGAL_PHONEETIC[] = "000";
static const char* keyboard_phonetic[] = {
ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,ILLEGAL_PHONEETIC,
"ㄇ","ㄖ","ㄏ","ㄎ","ㄍ","ㄑ","ㄕ","ㄘ","ㄛ","ㄨ","ㄜ","ㄠ", //0x00~0x0F
"ㄩ","ㄙ","ㄟ","ㄣ","ㄆ","ㄐ","ㄋ","ㄔ","ㄧ","ㄒ","ㄊ","ㄌ","ㄗ","ㄈ","ㄅ","ㄉ", //0x10~0x1F
"ˇ","ˋ","ㄓ","ˊ","˙","ㄚ","ㄞ","ㄢ","換行", ILLEGAL_PHONEETIC, "←", ILLEGAL_PHONEETIC,
" ","ㄦ","+","{", //0x20~0x2F
"}","|",ILLEGAL_PHONEETIC,"ㄤ","\"","~","ㄝ","ㄡ","ㄥ" //0x30~0x38
};
#define REPORT_ID 0x01
// from USB HID Specification 1.1, Appendix B.1
static const uint8_t hid_descriptor_keyboard_boot_mode[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xa1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID 1
// Modifier byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x05, 0x07, // Usage Page (Key codes)
0x19, 0xe0, // Usage Minimum (Keyboard LeftControl)
0x29, 0xe7, // Usage Maxium (Keyboard Right GUI)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Reserved byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x03, // Input (Constant, Variable, Absolute)
// LED report + padding
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maxium (Kana)
0x91, 0x02, // Output (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x03, // Output (Constant, Variable, Absolute)
// Keycodes
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xff, // Logical Maximum (1)
0x05, 0x07, // Usage Page (Key codes)
0x19, 0x00, // Usage Minimum (Reserved (no event indicated))
0x29, 0xff, // Usage Maxium (Reserved)
0x81, 0x00, // Input (Data, Array)
0xc0, // End collection
};
#endif
zh_TW.kb.c
#include "pico/stdlib.h"
#include "stdio.h"
#include "pico_lvgl.h"
#include "jf_font.h"
extern uint8_t lvgl_kb_left[4];
extern uint8_t lvgl_kb_right[4];
const char* font_map[] = {
"1","2","3","4","5","6","7","8","9","0","選字","\n",
"ㄅ","ㄉ","ˇ", "ˋ","ㄓ","ˊ", "˙","ㄚ","ㄞ","ㄢ","ㄦ","\n",
"ㄆ","ㄊ","ㄍ","ㄐ","ㄔ","ㄗ","ㄧ","ㄛ","ㄟ","ㄣ","←","\n",
"ㄇ","ㄋ","ㄎ","ㄑ","ㄕ","ㄘ","ㄨ","ㄜ","ㄠ","ㄤ","換行","\n",
"ㄈ","ㄌ","ㄏ","ㄒ","ㄖ","ㄙ","ㄩ","ㄝ","ㄡ","ㄥ",",","\n",
"英","數","〈"," ","〉","﹀","︿","。",""
};
const uint16_t KB_BTN = LV_KEYBOARD_CTRL_BTN_FLAGS|1;
const uint16_t KB_HIDDEN = LV_BTNMATRIX_CTRL_HIDDEN | 1;
const uint16_t space_key_ctl = LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_NO_REPEAT|2;
lv_btnmatrix_ctrl_t font_ctl_map[] = {
KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_HIDDEN,KB_BTN,
KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,LV_BTNMATRIX_CTRL_CHECKED|2,
KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,LV_BTNMATRIX_CTRL_CHECKED|2,
KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,KB_BTN,
LV_BTNMATRIX_CTRL_CHECKED|2,LV_BTNMATRIX_CTRL_CHECKED|2,KB_BTN,KB_BTN|6,KB_BTN,KB_BTN,KB_BTN, KB_BTN
};
void set_zh_TW_keyboard(lv_obj_t *kb) {
lv_obj_set_style_text_font(kb, &jf_font, LV_PART_ITEMS);
lv_keyboard_set_map(kb, LV_KEYBOARD_MODE_USER_1, font_map, font_ctl_map);
lv_obj_set_style_text_font(kb, lv_font_default(), LV_PART_ITEMS);
}