【2024 DigiKey Creative Competition】Use esp32-s3-lcd-ev-board to make a Huarongdao puzzle game
[Copy link]
This post was last edited by aramy on 2024-10-17 16:27
This time, I participated in the 2024 DigiKey "Sense Everything, Enjoy Life" competition. The board I chose is "esp32-s3-lcd-ev-board". This board is very luxuriously equipped with a 480*480 touch screen. Such a large screen can make the microcontroller interact with people very well.
- Project IntroductionESP32
-S3-LCD-EV-Board is a screen interaction development board based on the ESP32-S3 chip. By matching different types of LCD daughter boards, it can drive LCD displays with IIC, SPI, 8080 and RGB interfaces. At the same time, it is also equipped with a dual microphone array, supports voice recognition and near/far-field voice wake-up, and has touch screen interaction and voice interaction functions, meeting users' development needs for touch screen application products with different resolutions and interfaces. This project uses ESP32-S3-LCD-EV-Board plus a 480x480 LCD touch screen to complete the classic game "Huarongdao" jigsaw puzzle game.
-
- System Block Diagram
The ESP32-S3-LCD-EV-Board development board has a 480X480 touch screen, which is very suitable for interaction with users by touch. The project uses LVGL for graphic display and user interaction. The screen is divided into two parts: the game area and the control area.
The control area is responsible for controlling the game difficulty level and provides three buttons. One button is the exit button, which can exit the game. The other two buttons are the game difficulty adjustment buttons, which can adjust the game difficulty. There are 16 levels of difficulty (0~15). After adjusting the difficulty, the game interface will be reinitialized according to the current difficulty.
The game area displays the current position of each tile. There are four types of tiles in total. Each tile can be moved in four directions: up, down, left, and right. Users can move the corresponding tile in the game area by touching it. The system determines whether the current tile meets the moving conditions. If the conditions are met, the game area is redrawn to achieve the effect of moving the tile.
When the tile meets the victory conditions, you win and upgrade to the next difficulty level.
The system development uses esp-idf, the version is esp-idf 5.2.1, vscode is used as the development tool, and LVGL8.4.0 is selected as the library for UI development.
3. Functional description of each part
- Basic framework: Use the official example library as the basic framework.
After downloading the official example library from https://github.com/espressif/esp-dev-kits, find the lvgl_demos project in the examples under esp32-s3-lcd-ev-board as the basic project, and add your own functions on this basis.
- Now esp-idf uses the component method for programming, and the components cannot be modified. Here, move the lvgl component to the local computer. Create a "components" folder, move the lvgl__lvgl folder under the "managed_components" folder to the "components" folder, and rename it to lvgl.
Modify the CMakeLists.txt file in the main folder.
set(LV_DEMO_DIR ../components/lvgl/demos)
file(GLOB_RECURSE LV_DEMOS_SOURCES ${LV_DEMO_DIR}/*.c)
- In the main folder, delete the original "ui_printer" and "ui_tuner" folders. These two folders are not used in the project. Then create a game folder, and create a folder "huarongdao" under game to store your own game code files. Finally, you need to modify the CMakeLists.txt file.
- Modify the code. In the main.c main function, first introduce your own header function #include "huarongdao/huaorngdao.h". In the main function, keep the lvgl initialization part of the official routine, delete the rest, and add the game's calling function.
void app_main(void)
{
bsp_i2c_init();
lv_disp_t *disp = bsp_display_start();
ESP_LOGI(TAG, "Display LVGL demo");
/**
* To avoid errors caused by multiple tasks simultaneously accessing LVGL,
* should acquire a lock before operating on LVGL.
*/
bsp_display_lock(0);
huarongdao();
/* Release the lock */
bsp_display_unlock();
}
-
- The game entry function huarongdao() is where the game interface is initialized, including drawing the game background image, drawing buttons, and adding callback events to the buttons. The callback event for the game to interact with the user is "move_obj_cb", and this method drives the operation of the entire game.
static void move_obj_cb(lv_event_t *e)
{
static lv_point_t click_point1, click_point2;
int movex, movey, direction;
game_obj_type *stage_data = (game_obj_type *)e->user_data;
if (e->code == LV_EVENT_PRESSED)
{
lv_indev_get_point(lv_indev_get_act(), &click_point1);
return;
}
if (e->code == LV_EVENT_RELEASED)
{
lv_indev_get_point(lv_indev_get_act(), &click_point2);
movex = click_point2.x - click_point1.x;
movey = click_point2.y - click_point1.y;
if ((movex == 0 && movey == 0) || (movex == movey) || (movex == -movey))
return;
if ((movex < 0 && movey < 0 && movex > movey) || (movex > 0 && movey < 0 && movex < -movey))
direction = up;
if ((movex > 0 && movey < 0 && movex > -movey) || (movex > 0 && movey > 0 && movex > movey))
direction = right;
if ((movex < 0 && movey < 0 && movex < movey) || (movex < 0 && movey > 0 && movex < -movey))
direction = left;
if ((movex < 0 && movey > 0 && movex > -movey) || (movex > 0 && movey > 0 && movex < movey))
direction = down;
if (direction == up)
{
if (stage_data->obj_type == little)
{
if (stage_data->y == 0)
return;
if (game_map[stage_data->y - 1][stage_data->x] == 0)
{
game_map[stage_data->y - 1][stage_data->x] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->y--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == big)
{
if (stage_data->y == 0)
return;
if (game_map[stage_data->y - 1][stage_data->x] == 0 && game_map[stage_data->y - 1][(stage_data->x) + 1] == 0)
{
game_map[stage_data->y - 1][stage_data->x] = 1;
game_map[stage_data->y - 1][(stage_data->x) + 1] = 1;
game_map[stage_data->y + 1][stage_data->x] = 0;
game_map[stage_data->y + 1][(stage_data->x) + 1] = 0;
stage_data->y--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == hor)
{
if (stage_data->y == 0)
return;
if (game_map[stage_data->y - 1][stage_data->x] == 0 && game_map[stage_data->y - 1][(stage_data->x) + 1] == 0)
{
game_map[stage_data->y - 1][stage_data->x] = 1;
game_map[stage_data->y - 1][(stage_data->x) + 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y][(stage_data->x) + 1] = 0;
stage_data->y--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == ver)
{
if (stage_data->y == 0)
return;
if (game_map[stage_data->y - 1][stage_data->x] == 0)
{
game_map[stage_data->y - 1][stage_data->x] = 1;
game_map[stage_data->y + 1][stage_data->x] = 0;
stage_data->y--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
}
if (direction == down)
{
if (stage_data->obj_type == little)
{
if (stage_data->y == 4)
return;
if (game_map[stage_data->y + 1][stage_data->x] == 0)
{
game_map[stage_data->y + 1][stage_data->x] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->y++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == big)
{
if (stage_data->y == 3)
return;
if (game_map[stage_data->y + 2][stage_data->x] == 0 && game_map[stage_data->y + 2][(stage_data->x) + 1] == 0)
{
game_map[stage_data->y + 2][stage_data->x] = 1;
game_map[stage_data->y + 2][(stage_data->x) + 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y][(stage_data->x) + 1] = 0;
stage_data->y++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == hor)
{
if (stage_data->y == 4)
return;
if (game_map[stage_data->y + 1][stage_data->x] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 1] == 0)
{
game_map[stage_data->y + 1][stage_data->x] = 1;
game_map[stage_data->y + 1][(stage_data->x) + 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y][(stage_data->x) + 1] = 0;
stage_data->y++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == ver)
{
if (stage_data->y == 3)
return;
if (game_map[stage_data->y + 2][stage_data->x] == 0)
{
game_map[stage_data->y + 2][stage_data->x] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->y++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
}
if (direction == left)
{
if (stage_data->obj_type == little)
{
if (stage_data->x == 0)
return;
if (game_map[stage_data->y][stage_data->x - 1] == 0)
{
game_map[stage_data->y][stage_data->x - 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->x--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == big)
{
if (stage_data->x == 0)
return;
if (game_map[stage_data->y][stage_data->x - 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) - 1] == 0)
{
game_map[stage_data->y][stage_data->x - 1] = 1;
game_map[stage_data->y + 1][(stage_data->x) - 1] = 1;
game_map[stage_data->y][stage_data->x + 1] = 0;
game_map[stage_data->y + 1][(stage_data->x) + 1] = 0;
stage_data->x--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == hor)
{
if (stage_data->x == 0)
return;
if (game_map[stage_data->y][stage_data->x - 1] == 0)
{
game_map[stage_data->y][stage_data->x - 1] = 1;
game_map[stage_data->y][stage_data->x + 1] = 0;
stage_data->x--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == ver)
{
if (stage_data->x == 0)
return;
if (game_map[stage_data->y][stage_data->x - 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) - 1] == 0)
{
game_map[stage_data->y][stage_data->x - 1] = 1;
game_map[stage_data->y + 1][stage_data->x - 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y + 1][stage_data->x] = 0;
stage_data->x--;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
}
if (direction == right)
{
if (stage_data->obj_type == little)
{
if (stage_data->x == 3)
return;
if (game_map[stage_data->y][stage_data->x + 1] == 0)
{
game_map[stage_data->y][stage_data->x + 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->x++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == big)
{
if (stage_data->x == 2)
return;
if (game_map[stage_data->y][stage_data->x + 2] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 2] == 0)
{
game_map[stage_data->y][stage_data->x + 2] = 1;
game_map[stage_data->y + 1][(stage_data->x) + 2] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y + 1][(stage_data->x)] = 0;
stage_data->x++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == hor)
{
if (stage_data->x == 2)
return;
if (game_map[stage_data->y][stage_data->x + 2] == 0)
{
game_map[stage_data->y][stage_data->x + 2] = 1;
game_map[stage_data->y][stage_data->x] = 0;
stage_data->x++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
if (stage_data->obj_type == ver)
{
if (stage_data->x == 3)
return;
if (game_map[stage_data->y][stage_data->x + 1] == 0 && game_map[stage_data->y + 1][(stage_data->x) + 1] == 0)
{
game_map[stage_data->y][stage_data->x + 1] = 1;
game_map[stage_data->y + 1][stage_data->x + 1] = 1;
game_map[stage_data->y][stage_data->x] = 0;
game_map[stage_data->y + 1][stage_data->x] = 0;
stage_data->x++;
step_count++;
lv_label_set_text_fmt(step_lable, "STEP:%d", step_count);
lv_obj_set_pos(stage_data->obj, lv_pct((stage_data->x) * 25), lv_pct((stage_data->y) * 20));
}
}
}
if (stage_data->obj_type == big && stage_data->x == 1 && stage_data->y == 3)
{
lv_obj_t *clear_lable = lv_label_create(game_window);
lv_label_set_text(clear_lable, "STAGE CLEAR");
lv_obj_set_style_text_color(clear_lable, lv_color_hex(0xffffff), 0);
lv_obj_center(clear_lable);
if (current_stage < max_stage - 1)
{
current_stage++;
}
stage_clear();
}
}
}
4. Source Code
5. Demonstration video of the work’s functions
6. Project Summary
I am very sad that I failed to achieve the goal I set at the beginning! I originally wanted to implement machine learning on this board, but I was unable to do so due to my limited ability. LVGL is a very powerful tool for UI implementation, but I always feel that the versions are a bit confusing, and the methods of different versions are very different. The learning cost is a bit too high, but it is still very useful as a bridge between microcontrollers and people. It senses everything through microcontrollers and sensors, and then displays it with a suitable UI to interact with people. I like the ESP32-S3-LCD-EV-Board development board very much, and I hope to keep this board with the help of this project!
|