Macro Pad
Tutorial
This example shows how to use Termod S3 as a macro pad.
We use LVGL to make beautiful UI. Here also uses lv_helper
Note
If you haven’t download the code:
Download examples from github termod-s3
Unzip the downloaded termod-s3-main.zip
Or just clone the repository
git clone https://github.com/TAMCTec/termod-s3.git
Open termod-s3/examples/macro_pad/macro_pad.ino
with Arduino IDE.
This example use a 22px font LV_FONT_MONTSERRAT_22
, you need to enable it in lv_conf.h
, the conf file mentioned in Install LVGL Library (Optional).
Open the file, and find the following code, change the 0 to 1 to enable the font.
#define LV_FONT_MONTSERRAT_22 1
Make Sure that USB Mode is set to USB OTG under Tools, and Remember to select ESP32S3 Dev Module
and port, then click upload.
Make Icons
To make your own icons, get a picture, better be a png with transparent background, resize it to about 50x50, then use lvgl online image converter to convert it to C array.
Set the output name, it will be the name of the image data variable, so make it “code friendly”. Set Color format to CF_TRUE_COLOR_ALPHA
and output to C array
. Click Convert
, it will download a .c
file.
Then, copy the file to your project, and change the first few line, or it will raise compile error fatal error: lvgl/lvgl.h: No such file or directory
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
To
#include "lvgl.h"
Now add a line to .ino
file to declare it.
LV_IMG_DECLARE(<name>);
That’s it, you can now use it to create a button:
createIconButton(&<name>, 0, 0, <onPressed>, <onReleased>, <onTap>);
You can see all above in the example for a reference.
Create a shortcut
Some Apps have a keyboard shortcut like CONSUMER_CONTROL_CALCULATOR
. You can launch it with ConsumerControl
. Others you need to create a keyboard shortcut,
and simulate the shortcut with Termod S3.
For Windows 10 and 11, you can make a keyboard shortcut to a desktop shortcut. First, create a shortcut of a app to desktop. Then, right click the shortcut, click Properties
.
You will see a shortcut options, click on it and press a shortcut key, like Ctrl+Alt+Shift+1
. Then click Apply
and OK
.
Then in code, simulate it like in the example openKicad
.
void openKicad(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press('1');
Keyboard.releaseAll();
}
You can change keys.
To control keyboard, use
Keyboard
, checkout all keys under USBHIDKeyboard.hTo control volume and music, use
ConsumerControl
, checkout all controls under USBHIDConsumerControl.h
#if ARDUINO_USB_MODE
#warning This sketch should be used when USB is in OTG mode
void setup(){}
void loop(){}
#else
#include "lv_helper.h"
#include "USB.h"
#include "USBHIDKeyboard.h"
#include "USBHIDConsumerControl.h"
USBHIDConsumerControl ConsumerControl;
USBHIDKeyboard Keyboard;
#define CONSUMER_CONTROL_INTERNET_BROWSER 0x0196
LV_IMG_DECLARE(calculator_icon);
LV_IMG_DECLARE(kicad_icon);
LV_IMG_DECLARE(arduino_icon);
LV_IMG_DECLARE(vscode_icon);
#define KEYBOARD_LAYOUT_MAC 0
#define KEYBOARD_LAYOUT_WINDOWS 1
// If you are using a Mac, set this to KEYBOARD_LAYOUT_MAC
#define KEYBOARD_LAYOUT KEYBOARD_LAYOUT_WINDOWS
#define LAYOUT_WIDTH 4
#define LAYOUT_HEIGHT 3
#define PADDING 2
#define BUTTON_WIDTH 320 / LAYOUT_WIDTH - (2 * PADDING)
#define BUTTON_HEIGHT 240 / LAYOUT_HEIGHT - (2 * PADDING)
static lv_style_t pressedStyle;
lv_obj_t* createButton(int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*));
void createTextButton(char* text, int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*));
void createIconButton(const lv_img_dsc_t *image, int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*));
void setup() {
Serial.begin(115200);
lh_init(DISPLAY_LANDSCAPE);
// Create button style, when button is pressed, glow it
lv_style_init(&pressedStyle);
lv_style_set_border_color(&pressedStyle, lv_color_hex(0x33dddd));
lv_style_set_shadow_color(&pressedStyle, lv_color_hex(0x33dddd));
lv_style_set_shadow_width(&pressedStyle, 2);
Keyboard.begin();
ConsumerControl.begin();
USB.begin();
createIconButton(&calculator_icon, 0, 0, NULL, NULL, openCalculator);
createIconButton(&kicad_icon, 1, 0, NULL, NULL, openKicad);
createIconButton(&arduino_icon, 2, 0, NULL, NULL, openArduino);
createIconButton(&vscode_icon, 3, 0, NULL, NULL, openVSCode);
createTextButton(LV_SYMBOL_VOLUME_MID, 0, 1, volumeDownPressed, volumeDownReleased, NULL);
createTextButton(LV_SYMBOL_VOLUME_MAX, 1, 1, volumeUpPressed, volumeUpReleased, NULL);
createTextButton(LV_SYMBOL_MUTE, 2, 1, NULL, NULL, mute);
createTextButton(LV_SYMBOL_HOME, 3, 1, NULL, NULL, home);
createTextButton(LV_SYMBOL_COPY, 0, 2, NULL, NULL, copy);
createTextButton(LV_SYMBOL_PASTE, 1, 2, NULL, NULL, paste);
createTextButton(LV_SYMBOL_LEFT, 2, 2, NULL, NULL, leftDesktop);
createTextButton(LV_SYMBOL_RIGHT, 3, 2, NULL, NULL, rightDesktop);
}
void loop() {
lv_timer_handler();
}
void openCalculator(_lv_event_t* event) {
ConsumerControl.press(CONSUMER_CONTROL_CALCULATOR);
ConsumerControl.release();
}
void openKicad(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press('1');
Keyboard.releaseAll();
}
void openArduino(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press('2');
Keyboard.releaseAll();
}
void openVSCode(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press('3');
Keyboard.releaseAll();
}
void volumeDownPressed(_lv_event_t* event) {
ConsumerControl.press(CONSUMER_CONTROL_VOLUME_DECREMENT);
}
void volumeDownReleased(_lv_event_t* event) {
ConsumerControl.release();
}
void volumeUpPressed(_lv_event_t* event) {
ConsumerControl.press(CONSUMER_CONTROL_VOLUME_INCREMENT);
}
void volumeUpReleased(_lv_event_t* event) {
ConsumerControl.release();
}
void mute(_lv_event_t* event) {
ConsumerControl.press(CONSUMER_CONTROL_MUTE);
ConsumerControl.release();
}
void copy(_lv_event_t* event) {
#if KEYBOARD_LAYOUT == KEYBOARD_LAYOUT_WINDOWS
Keyboard.press(KEY_LEFT_CTRL);
#else
Keyboard.press(KEY_LEFT_GUI);
#endif
Keyboard.press('c');
Keyboard.releaseAll();
}
void paste(_lv_event_t* event) {
#if KEYBOARD_LAYOUT == KEYBOARD_LAYOUT_WINDOWS
Keyboard.press(KEY_LEFT_CTRL);
#else
Keyboard.press(KEY_LEFT_GUI);
#endif
Keyboard.press('v');
Keyboard.releaseAll();
}
void home(_lv_event_t* event) {
#if KEYBOARD_LAYOUT == KEYBOARD_LAYOUT_WINDOWS
Keyboard.press(KEY_LEFT_GUI);
Keyboard.press('d');
#endif
Keyboard.releaseAll();
}
void leftDesktop(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
#if KEYBOARD_LAYOUT == KEYBOARD_LAYOUT_WINDOWS
Keyboard.press(KEY_LEFT_GUI);
#endif
Keyboard.press(KEY_LEFT_ARROW);
Keyboard.releaseAll();
}
void rightDesktop(_lv_event_t* event) {
Keyboard.press(KEY_LEFT_CTRL);
#if KEYBOARD_LAYOUT == KEYBOARD_LAYOUT_WINDOWS
Keyboard.press(KEY_LEFT_GUI);
#endif
Keyboard.press(KEY_RIGHT_ARROW);
Keyboard.releaseAll();
}
// create a button
lv_obj_t* createButton(int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*)) {
int top = x * 80 + PADDING;
int left = y * 80 + PADDING;
lv_obj_t* btn = lv_obj_create(lv_scr_act());
lv_obj_set_size(btn, BUTTON_WIDTH, BUTTON_HEIGHT);
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, top, left);
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_style(btn, &pressedStyle, LV_STATE_PRESSED);
if (onPressed != NULL) {
lv_obj_add_event_cb(btn, onPressed, LV_EVENT_PRESSED, NULL);
}
if (onReleased != NULL) {
lv_obj_add_event_cb(btn, onReleased, LV_EVENT_RELEASED, NULL);
}
if (onTap != NULL) {
lv_obj_add_event_cb(btn, onTap, LV_EVENT_CLICKED, NULL);
}
return btn;
}
void createTextButton(char* text, int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*)) {
lv_obj_t* btn = createButton(x, y, onPressed, onReleased, onTap);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, text);
lv_obj_set_style_text_font(label, &lv_font_montserrat_22, 0);
lv_obj_center(label);
}
void createIconButton(const lv_img_dsc_t *image, int x, int y, void (*onPressed)(_lv_event_t*), void (*onReleased)(_lv_event_t*), void (*onTap)(_lv_event_t*)){
lv_obj_t* btn = createButton(x, y, onPressed, onReleased, onTap);
lv_obj_t* img = lv_img_create(btn);
lv_img_set_src(img, image);
lv_obj_center(img);
}
#endif /* ARDUINO_USB_MODE */
#include "lv_helper.h"
TFT_eSPI lh_tft = TFT_eSPI();
TAMC_FT62X6 lh_tp = TAMC_FT62X6();
static lv_disp_draw_buf_t lh_draw_buf;
static lv_color_t lh_buf[ DISPLAY_WIDTH * 10 ];
static lv_disp_drv_t lh_disp_drv;
static lv_indev_drv_t lh_indev_drv;
uint16_t width, height;
void lh_init(int rotation){
Wire.begin();
lh_tp.begin();
lv_init();
lh_tft.begin();
if (rotation == 1 || rotation == 3){
width = DISPLAY_HEIGHT;
height = DISPLAY_WIDTH;
} else {
width = DISPLAY_WIDTH;
height = DISPLAY_HEIGHT;
}
lh_tft.setRotation(rotation);
lh_tp.setRotation(rotation);
lv_disp_draw_buf_init( &lh_draw_buf, lh_buf, NULL, DISPLAY_WIDTH * 10 );
/*Initialize the display*/
lv_disp_drv_init( &lh_disp_drv );
/*Change the following line to your display resolution*/
lh_disp_drv.hor_res = width;
lh_disp_drv.ver_res = height;
lh_disp_drv.flush_cb = lh_disp_flush;
lh_disp_drv.draw_buf = &lh_draw_buf;
lv_disp_drv_register( &lh_disp_drv );
/*Initialize the (dummy) input device driver*/
lv_indev_drv_init( &lh_indev_drv );
lh_indev_drv.type = LV_INDEV_TYPE_POINTER;
lh_indev_drv.read_cb = lh_touchpad_read;
lv_indev_drv_register( &lh_indev_drv );
}
/* Display flushing */
void lh_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
lh_tft.startWrite();
lh_tft.setAddrWindow( area->x1, area->y1, w, h );
lh_tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
lh_tft.endWrite();
lv_disp_flush_ready(disp);
}
/*Read the touchpad*/
void lh_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) {
lh_tp.read();
if (!lh_tp.isTouched) {
data->state = LV_INDEV_STATE_RELEASED;
}
else{
data->state = LV_INDEV_STATE_PRESSED;
/*Set the coordinates*/
data->point.x = lh_tp.points[0].x;
data->point.y = lh_tp.points[0].y;
}
}
#ifndef LV_HELPER_H
#define LV_HELPER_H
#include <lvgl.h>
#include "TAMC_FT62X6.h"
#include "Wire.h"
#include <TFT_eSPI.h>
#define DISPLAY_PORTRAIT 2
#define DISPLAY_LANDSCAPE 3
#define DISPLAY_PORTRAIT_FLIP 0
#define DISPLAY_LANDSCAPE_FLIP 1
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
/* Display flushing */
void lh_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
/*Read the touchpad*/
void lh_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data);
void lh_init(int rotation);
#endif // LV_HELPER_H