Initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
build/
|
||||||
|
managed_components/
|
||||||
|
sdkconfig
|
||||||
|
sdkconfig.old
|
||||||
|
dependencies.lock
|
||||||
4
CMakeLists.txt
Normal file
4
CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(esp32p4_tarot VERSION 0.1.0)
|
||||||
195
README.md
Normal file
195
README.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# ESP32-P4 Tarot Touchscreen App
|
||||||
|
|
||||||
|
ESP-IDF + LVGL tarot reading app for the Waveshare `ESP32-P4-86-Panel-ETH-2RO`. The first version is intentionally simple and stable: it draws unique tarot cards, renders card art on-device, persists settings in NVS, and does not require a microSD card.
|
||||||
|
|
||||||
|
## What Is Included
|
||||||
|
|
||||||
|
- ESP-IDF project scaffold
|
||||||
|
- LVGL home, reading, and settings screens
|
||||||
|
- Full 78-card tarot deck data structure
|
||||||
|
- 3-card and 8-card spread support
|
||||||
|
- `esp_random()` card drawing with duplicate prevention
|
||||||
|
- Upright and reversed cards
|
||||||
|
- On-device generated card art
|
||||||
|
- NVS-backed settings
|
||||||
|
- Waveshare BSP display, touch, and backlight integration
|
||||||
|
|
||||||
|
## Project Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
main/
|
||||||
|
app_main.c
|
||||||
|
card_assets.c
|
||||||
|
card_assets.h
|
||||||
|
tarot_deck.c
|
||||||
|
tarot_deck.h
|
||||||
|
tarot_logic.c
|
||||||
|
tarot_logic.h
|
||||||
|
ui_main.c
|
||||||
|
ui_main.h
|
||||||
|
ui_settings.c
|
||||||
|
ui_settings.h
|
||||||
|
settings.c
|
||||||
|
settings.h
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target Hardware
|
||||||
|
|
||||||
|
This project targets the Waveshare `ESP32-P4-86-Panel-ETH-2RO`, part of the ESP32-P4 4-inch 86-panel family.
|
||||||
|
|
||||||
|
Hardware assumptions:
|
||||||
|
|
||||||
|
- 4-inch 720 x 720 IPS display
|
||||||
|
- MIPI-DSI display interface
|
||||||
|
- ST7703 display driver through Waveshare BSP
|
||||||
|
- GT911 capacitive touch
|
||||||
|
- microSD/TF slot over SDMMC/SDIO, currently not required by this app
|
||||||
|
- Onboard 32 MB flash and 32 MB PSRAM
|
||||||
|
- Ethernet, RS485, and 2 relay outputs present but not used by this app yet
|
||||||
|
|
||||||
|
The board support is handled through the Waveshare ESP-IDF BSP component:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
waveshare/esp32_p4_wifi6_touch_lcd_4b
|
||||||
|
```
|
||||||
|
|
||||||
|
The BSP initializes the MIPI display, GT911 touch, LVGL port, and backlight PWM.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Install and export ESP-IDF first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
. $IDF_PATH/export.sh
|
||||||
|
idf.py set-target esp32p4
|
||||||
|
idf.py menuconfig
|
||||||
|
idf.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
This project declares LVGL through `main/idf_component.yml`, so ESP-IDF's component manager will fetch it during build.
|
||||||
|
|
||||||
|
The Waveshare BSP is also declared there. It provides the display, touch, and backlight hardware integration for this panel.
|
||||||
|
|
||||||
|
## Flash And Monitor
|
||||||
|
|
||||||
|
```sh
|
||||||
|
idf.py -p /dev/tty.usbmodemXXXX flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the serial port with your board's port.
|
||||||
|
|
||||||
|
## On-Device Card Art
|
||||||
|
|
||||||
|
No SD card is required. `main/card_assets.c` renders a compact tarot card visual directly with LVGL widgets using deck metadata compiled into firmware.
|
||||||
|
|
||||||
|
This is deliberate for the first device build:
|
||||||
|
|
||||||
|
- No filesystem dependency
|
||||||
|
- No JPEG decoder dependency
|
||||||
|
- No missing-image crash path
|
||||||
|
- Fast startup on the panel
|
||||||
|
|
||||||
|
The deck still keeps a filename-style asset key for each card. When you want full illustrated embedded artwork, replace `card_assets_create_card_art()` with a lookup table from card id to embedded LVGL image descriptors.
|
||||||
|
|
||||||
|
## Embedded Asset Naming Convention
|
||||||
|
|
||||||
|
Major arcana:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00_fool.jpg
|
||||||
|
01_magician.jpg
|
||||||
|
02_high_priestess.jpg
|
||||||
|
03_empress.jpg
|
||||||
|
04_emperor.jpg
|
||||||
|
05_hierophant.jpg
|
||||||
|
06_lovers.jpg
|
||||||
|
07_chariot.jpg
|
||||||
|
08_strength.jpg
|
||||||
|
09_hermit.jpg
|
||||||
|
10_wheel_of_fortune.jpg
|
||||||
|
11_justice.jpg
|
||||||
|
12_hanged_man.jpg
|
||||||
|
13_death.jpg
|
||||||
|
14_temperance.jpg
|
||||||
|
15_devil.jpg
|
||||||
|
16_tower.jpg
|
||||||
|
17_star.jpg
|
||||||
|
18_moon.jpg
|
||||||
|
19_sun.jpg
|
||||||
|
20_judgement.jpg
|
||||||
|
21_world.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Minor arcana:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wands_ace.jpg
|
||||||
|
wands_02.jpg
|
||||||
|
wands_03.jpg
|
||||||
|
wands_04.jpg
|
||||||
|
wands_05.jpg
|
||||||
|
wands_06.jpg
|
||||||
|
wands_07.jpg
|
||||||
|
wands_08.jpg
|
||||||
|
wands_09.jpg
|
||||||
|
wands_10.jpg
|
||||||
|
wands_page.jpg
|
||||||
|
wands_knight.jpg
|
||||||
|
wands_queen.jpg
|
||||||
|
wands_king.jpg
|
||||||
|
|
||||||
|
cups_ace.jpg
|
||||||
|
cups_02.jpg
|
||||||
|
cups_03.jpg
|
||||||
|
cups_04.jpg
|
||||||
|
cups_05.jpg
|
||||||
|
cups_06.jpg
|
||||||
|
cups_07.jpg
|
||||||
|
cups_08.jpg
|
||||||
|
cups_09.jpg
|
||||||
|
cups_10.jpg
|
||||||
|
cups_page.jpg
|
||||||
|
cups_knight.jpg
|
||||||
|
cups_queen.jpg
|
||||||
|
cups_king.jpg
|
||||||
|
|
||||||
|
swords_ace.jpg
|
||||||
|
swords_02.jpg
|
||||||
|
swords_03.jpg
|
||||||
|
swords_04.jpg
|
||||||
|
swords_05.jpg
|
||||||
|
swords_06.jpg
|
||||||
|
swords_07.jpg
|
||||||
|
swords_08.jpg
|
||||||
|
swords_09.jpg
|
||||||
|
swords_10.jpg
|
||||||
|
swords_page.jpg
|
||||||
|
swords_knight.jpg
|
||||||
|
swords_queen.jpg
|
||||||
|
swords_king.jpg
|
||||||
|
|
||||||
|
pentacles_ace.jpg
|
||||||
|
pentacles_02.jpg
|
||||||
|
pentacles_03.jpg
|
||||||
|
pentacles_04.jpg
|
||||||
|
pentacles_05.jpg
|
||||||
|
pentacles_06.jpg
|
||||||
|
pentacles_07.jpg
|
||||||
|
pentacles_08.jpg
|
||||||
|
pentacles_09.jpg
|
||||||
|
pentacles_10.jpg
|
||||||
|
pentacles_page.jpg
|
||||||
|
pentacles_knight.jpg
|
||||||
|
pentacles_queen.jpg
|
||||||
|
pentacles_king.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Settings Saved In NVS
|
||||||
|
|
||||||
|
- `reversed_cards_enabled`
|
||||||
|
- `save_readings_enabled`
|
||||||
|
- `brightness`
|
||||||
|
- `theme`
|
||||||
|
- `selected_spread`
|
||||||
|
|
||||||
|
The `save_readings_enabled` flag is persisted and exposed in UI. Actual reading history storage is intentionally left for the next version so this first build stays stable.
|
||||||
8
components/esp32_p4_wifi6_touch_lcd_4b/CMakeLists.txt
Normal file
8
components/esp32_p4_wifi6_touch_lcd_4b/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
SRCS "esp32_p4_wifi6_touch_lcd_4b.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
PRIV_INCLUDE_DIRS "priv_include"
|
||||||
|
REQUIRES driver
|
||||||
|
PRIV_REQUIRES esp_lcd usb spiffs fatfs
|
||||||
|
)
|
||||||
140
components/esp32_p4_wifi6_touch_lcd_4b/Kconfig
Normal file
140
components/esp32_p4_wifi6_touch_lcd_4b/Kconfig
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
menu "Board Support Package(ESP32-P4)"
|
||||||
|
|
||||||
|
config BSP_ERROR_CHECK
|
||||||
|
bool "Enable error check in BSP"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Error check assert the application before returning the error code.
|
||||||
|
|
||||||
|
menu "I2C"
|
||||||
|
config BSP_I2C_NUM
|
||||||
|
int "I2C peripheral index"
|
||||||
|
default 1
|
||||||
|
range 0 1
|
||||||
|
help
|
||||||
|
ESP32P4 has two I2C peripherals, pick the one you want to use.
|
||||||
|
|
||||||
|
config BSP_I2C_FAST_MODE
|
||||||
|
bool "Enable I2C fast mode"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
I2C has two speed modes: normal (100kHz) and fast (400kHz).
|
||||||
|
|
||||||
|
config BSP_I2C_CLK_SPEED_HZ
|
||||||
|
int
|
||||||
|
default 400000 if BSP_I2C_FAST_MODE
|
||||||
|
default 100000
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "I2S"
|
||||||
|
config BSP_I2S_NUM
|
||||||
|
int "I2S peripheral index"
|
||||||
|
default 1
|
||||||
|
range 0 2
|
||||||
|
help
|
||||||
|
ESP32P4 has three I2S peripherals, pick the one you want to use.
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "uSD card - Virtual File System"
|
||||||
|
config BSP_SD_FORMAT_ON_MOUNT_FAIL
|
||||||
|
bool "Format uSD card if mounting fails"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
The SDMMC host will format (FAT) the uSD card if it fails to mount the filesystem.
|
||||||
|
|
||||||
|
config BSP_SD_MOUNT_POINT
|
||||||
|
string "uSD card mount point"
|
||||||
|
default "/sdcard"
|
||||||
|
help
|
||||||
|
Mount point of the uSD card in the Virtual File System
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "SPIFFS - Virtual File System"
|
||||||
|
config BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL
|
||||||
|
bool "Format SPIFFS if mounting fails"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Format SPIFFS if it fails to mount the filesystem.
|
||||||
|
|
||||||
|
config BSP_SPIFFS_MOUNT_POINT
|
||||||
|
string "SPIFFS mount point"
|
||||||
|
default "/spiffs"
|
||||||
|
help
|
||||||
|
Mount point of SPIFFS in the Virtual File System.
|
||||||
|
|
||||||
|
config BSP_SPIFFS_PARTITION_LABEL
|
||||||
|
string "Partition label of SPIFFS"
|
||||||
|
default "storage"
|
||||||
|
help
|
||||||
|
Partition label which stores SPIFFS.
|
||||||
|
|
||||||
|
config BSP_SPIFFS_MAX_FILES
|
||||||
|
int "Max files supported for SPIFFS VFS"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
Supported max files for SPIFFS in the Virtual File System.
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
menu "Display"
|
||||||
|
config BSP_LCD_DPI_BUFFER_NUMS
|
||||||
|
int "Set number of frame buffers"
|
||||||
|
default 1
|
||||||
|
range 1 3
|
||||||
|
help
|
||||||
|
Let DPI LCD driver create a specified number of frame-size buffers. Only when it is set to multiple can the avoiding tearing be turned on.
|
||||||
|
|
||||||
|
config BSP_DISPLAY_LVGL_AVOID_TEAR
|
||||||
|
bool "Avoid tearing effect"
|
||||||
|
depends on BSP_LCD_DPI_BUFFER_NUMS > 1
|
||||||
|
default "n"
|
||||||
|
help
|
||||||
|
Avoid tearing effect through LVGL buffer mode and double frame buffers of RGB LCD. This feature is only available for RGB LCD.
|
||||||
|
|
||||||
|
choice BSP_DISPLAY_LVGL_MODE
|
||||||
|
depends on BSP_DISPLAY_LVGL_AVOID_TEAR
|
||||||
|
prompt "Select LVGL buffer mode"
|
||||||
|
default BSP_DISPLAY_LVGL_FULL_REFRESH
|
||||||
|
config BSP_DISPLAY_LVGL_FULL_REFRESH
|
||||||
|
bool "Full refresh"
|
||||||
|
config BSP_DISPLAY_LVGL_DIRECT_MODE
|
||||||
|
bool "Direct mode"
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
config BSP_DISPLAY_BRIGHTNESS_LEDC_CH
|
||||||
|
int "LEDC channel index"
|
||||||
|
default 1
|
||||||
|
range 0 7
|
||||||
|
help
|
||||||
|
LEDC channel is used to generate PWM signal that controls display brightness.
|
||||||
|
Set LEDC index that should be used.
|
||||||
|
|
||||||
|
choice BSP_LCD_COLOR_FORMAT
|
||||||
|
prompt "Select LCD color format"
|
||||||
|
default BSP_LCD_COLOR_FORMAT_RGB565
|
||||||
|
help
|
||||||
|
Select the LCD color format RGB565/RGB888.
|
||||||
|
|
||||||
|
config BSP_LCD_COLOR_FORMAT_RGB565
|
||||||
|
bool "RGB565"
|
||||||
|
config BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
bool "RGB888"
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
choice BSP_LCD_TYPE
|
||||||
|
prompt "Select LCD type"
|
||||||
|
default BSP_LCD_TYPE_800_800_3_4_INCH
|
||||||
|
help
|
||||||
|
Select the LCD.
|
||||||
|
|
||||||
|
config BSP_LCD_TYPE_800_800_3_4_INCH
|
||||||
|
bool "Waveshare board with 800*800 3.4-inch Display"
|
||||||
|
config BSP_LCD_TYPE_720_720_4_INCH
|
||||||
|
bool "Waveshare board with 720*720 4-inch Display"
|
||||||
|
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
||||||
|
endmenu
|
||||||
202
components/esp32_p4_wifi6_touch_lcd_4b/LICENSE
Normal file
202
components/esp32_p4_wifi6_touch_lcd_4b/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
35
components/esp32_p4_wifi6_touch_lcd_4b/README.md
Normal file
35
components/esp32_p4_wifi6_touch_lcd_4b/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# BSP: Waveshare ESP32-P4-WIFI6-Touch-LCD-4B && ESP32-P4-86-Panel-ETH-2RO
|
||||||
|
|
||||||
|
[](https://components.espressif.com/components/waveshare/esp32_p4_wifi6_touch_lcd_4b)
|
||||||
|
|
||||||
|
This series of products is based on ESP32-P4 chip high-performance development board. It uses dual-core plus single-core RISC-V architecture design, and a 4-inch 720 × 720 resolution IPS touch display is mounted on the board
|
||||||
|
| HW version | BSP Version |
|
||||||
|
| :--------: | :---------: |
|
||||||
|
| [V1.0](http://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-4B) | ^1 |
|
||||||
|
|
||||||
|
|
||||||
|
## BackLight
|
||||||
|
```c
|
||||||
|
bsp_display_brightness_init();
|
||||||
|
|
||||||
|
bsp_display_backlight_on();
|
||||||
|
|
||||||
|
bsp_display_backlight_off();
|
||||||
|
|
||||||
|
bsp_display_brightness_set(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Autogenerated start: Dependencies -->
|
||||||
|
### Capabilities and dependencies
|
||||||
|
| Capability | Available | Component | Version |
|
||||||
|
|-------------|------------------|------------------------------------------------------------------------------------------------------------|---------|
|
||||||
|
| DISPLAY |:heavy_check_mark:| [waveshare/esp_lcd_st7703](https://components.espressif.com/components/waveshare/esp_lcd_st7703) | 1.0.1 |
|
||||||
|
| LVGL_PORT |:heavy_check_mark:| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 |
|
||||||
|
| TOUCH |:heavy_check_mark:| [espressif/esp_lcd_touch_gt911](https://components.espressif.com/components/espressif/esp_lcd_touch_gt911) | ^1 |
|
||||||
|
| BUTTONS | :x: | | |
|
||||||
|
| AUDIO |:heavy_check_mark:| [espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev) | 1.2.* |
|
||||||
|
|AUDIO_SPEAKER|:heavy_check_mark:| | |
|
||||||
|
| AUDIO_MIC |:heavy_check_mark:| | |
|
||||||
|
| SDCARD |:heavy_check_mark:| idf | >=5.3 |
|
||||||
|
| IMU | :x: | | |
|
||||||
|
<!-- Autogenerated end: Dependencies -->
|
||||||
@@ -0,0 +1,678 @@
|
|||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/ledc.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "esp_spiffs.h"
|
||||||
|
#include "esp_lcd_panel_ops.h"
|
||||||
|
#include "esp_lcd_mipi_dsi.h"
|
||||||
|
#include "esp_ldo_regulator.h"
|
||||||
|
#include "esp_vfs_fat.h"
|
||||||
|
#include "usb/usb_host.h"
|
||||||
|
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
|
||||||
|
#include "esp_lcd_st7703.h"
|
||||||
|
#include "bsp/esp32_p4_wifi6_touch_lcd_4b.h"
|
||||||
|
#include "bsp/display.h"
|
||||||
|
#include "bsp/touch.h"
|
||||||
|
#include "esp_lcd_touch_gt911.h"
|
||||||
|
#include "bsp_err_check.h"
|
||||||
|
#include "esp_codec_dev_defaults.h"
|
||||||
|
|
||||||
|
static const char *TAG = "ESP32_P4_4B";
|
||||||
|
|
||||||
|
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
static lv_indev_t *disp_indev = NULL;
|
||||||
|
#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
|
||||||
|
sdmmc_card_t *bsp_sdcard = NULL; // Global uSD card handler
|
||||||
|
static bool i2c_initialized = false;
|
||||||
|
static TaskHandle_t usb_host_task; // USB Host Library task
|
||||||
|
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
|
||||||
|
static i2c_master_bus_handle_t i2c_handle = NULL; // I2C Handle
|
||||||
|
#endif
|
||||||
|
static i2s_chan_handle_t i2s_tx_chan = NULL;
|
||||||
|
static i2s_chan_handle_t i2s_rx_chan = NULL;
|
||||||
|
static const audio_codec_data_if_t *i2s_data_if = NULL; /* Codec data interface */
|
||||||
|
#define BSP_ES7210_CODEC_ADDR ES7210_CODEC_DEFAULT_ADDR
|
||||||
|
|
||||||
|
/* Can be used for `i2s_std_gpio_config_t` and/or `i2s_std_config_t` initialization */
|
||||||
|
#define BSP_I2S_GPIO_CFG \
|
||||||
|
{ \
|
||||||
|
.mclk = BSP_I2S_MCLK, \
|
||||||
|
.bclk = BSP_I2S_SCLK, \
|
||||||
|
.ws = BSP_I2S_LCLK, \
|
||||||
|
.dout = BSP_I2S_DOUT, \
|
||||||
|
.din = BSP_I2S_DSIN, \
|
||||||
|
.invert_flags = { \
|
||||||
|
.mclk_inv = false, \
|
||||||
|
.bclk_inv = false, \
|
||||||
|
.ws_inv = false, \
|
||||||
|
}, \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This configuration is used by default in `bsp_extra_audio_init()` */
|
||||||
|
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate) \
|
||||||
|
{ \
|
||||||
|
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \
|
||||||
|
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
|
||||||
|
.gpio_cfg = BSP_I2S_GPIO_CFG, \
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_i2c_init(void)
|
||||||
|
{
|
||||||
|
/* I2C was initialized before */
|
||||||
|
if (i2c_initialized) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_master_bus_config_t i2c_bus_conf = {
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
.sda_io_num = BSP_I2C_SDA,
|
||||||
|
.scl_io_num = BSP_I2C_SCL,
|
||||||
|
.i2c_port = BSP_I2C_NUM,
|
||||||
|
};
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(i2c_new_master_bus(&i2c_bus_conf, &i2c_handle));
|
||||||
|
|
||||||
|
i2c_initialized = true;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_i2c_deinit(void)
|
||||||
|
{
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(i2c_del_master_bus(i2c_handle));
|
||||||
|
i2c_initialized = false;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_master_bus_handle_t bsp_i2c_get_handle(void)
|
||||||
|
{
|
||||||
|
return i2c_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t bsp_enable_ldo_vo4(void)
|
||||||
|
{
|
||||||
|
static esp_ldo_channel_handle_t vo4_chan = NULL;
|
||||||
|
esp_ldo_channel_config_t ldo_cfg = {
|
||||||
|
.chan_id = 4,
|
||||||
|
.voltage_mv = 3300,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(esp_ldo_acquire_channel(&ldo_cfg, &vo4_chan), TAG, "Acquire LDO VO4 channel failed");
|
||||||
|
ESP_LOGI(TAG, "LDO VO4 set to 3300mV");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_sdcard_mount(void)
|
||||||
|
{
|
||||||
|
const esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
|
#ifdef CONFIG_BSP_SD_FORMAT_ON_MOUNT_FAIL
|
||||||
|
.format_if_mount_failed = true,
|
||||||
|
#else
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
#endif
|
||||||
|
.max_files = 5,
|
||||||
|
.allocation_unit_size = 64 * 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||||
|
host.slot = SDMMC_HOST_SLOT_0;
|
||||||
|
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||||
|
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(bsp_enable_ldo_vo4(), TAG, "DSI PHY power failed");
|
||||||
|
|
||||||
|
const sdmmc_slot_config_t slot_config = {
|
||||||
|
/* SD card is connected to Slot 0 pins. Slot 0 uses IO MUX, so not specifying the pins here */
|
||||||
|
.cd = SDMMC_SLOT_NO_CD,
|
||||||
|
.wp = SDMMC_SLOT_NO_WP,
|
||||||
|
.width = 4,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return esp_vfs_fat_sdmmc_mount(BSP_SD_MOUNT_POINT, &host, &slot_config, &mount_config, &bsp_sdcard);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_sdcard_unmount(void)
|
||||||
|
{
|
||||||
|
return esp_vfs_fat_sdcard_unmount(BSP_SD_MOUNT_POINT, bsp_sdcard);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_spiffs_mount(void)
|
||||||
|
{
|
||||||
|
esp_vfs_spiffs_conf_t conf = {
|
||||||
|
.base_path = CONFIG_BSP_SPIFFS_MOUNT_POINT,
|
||||||
|
.partition_label = CONFIG_BSP_SPIFFS_PARTITION_LABEL,
|
||||||
|
.max_files = CONFIG_BSP_SPIFFS_MAX_FILES,
|
||||||
|
#ifdef CONFIG_BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL
|
||||||
|
.format_if_mount_failed = true,
|
||||||
|
#else
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret_val = esp_vfs_spiffs_register(&conf);
|
||||||
|
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(ret_val);
|
||||||
|
|
||||||
|
size_t total = 0, used = 0;
|
||||||
|
ret_val = esp_spiffs_info(conf.partition_label, &total, &used);
|
||||||
|
if (ret_val != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret_val));
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_spiffs_unmount(void)
|
||||||
|
{
|
||||||
|
return esp_vfs_spiffs_unregister(CONFIG_BSP_SPIFFS_PARTITION_LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* I2S Audio Function
|
||||||
|
*
|
||||||
|
**************************************************************************************************/
|
||||||
|
esp_err_t bsp_audio_init(const i2s_std_config_t *i2s_config)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
if (i2s_tx_chan && i2s_rx_chan) {
|
||||||
|
/* Audio was initialized before */
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup I2S peripheral */
|
||||||
|
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(CONFIG_BSP_I2S_NUM, I2S_ROLE_MASTER);
|
||||||
|
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(i2s_new_channel(&chan_cfg, &i2s_tx_chan, &i2s_rx_chan));
|
||||||
|
|
||||||
|
/* Setup I2S channels */
|
||||||
|
const i2s_std_config_t std_cfg_default = BSP_I2S_DUPLEX_MONO_CFG(22050);
|
||||||
|
const i2s_std_config_t *p_i2s_cfg = &std_cfg_default;
|
||||||
|
if (i2s_config != NULL) {
|
||||||
|
p_i2s_cfg = i2s_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2s_tx_chan != NULL) {
|
||||||
|
ESP_GOTO_ON_ERROR(i2s_channel_init_std_mode(i2s_tx_chan, p_i2s_cfg), err, TAG, "I2S channel initialization failed");
|
||||||
|
ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_tx_chan), err, TAG, "I2S enabling failed");
|
||||||
|
}
|
||||||
|
if (i2s_rx_chan != NULL) {
|
||||||
|
ESP_GOTO_ON_ERROR(i2s_channel_init_std_mode(i2s_rx_chan, p_i2s_cfg), err, TAG, "I2S channel initialization failed");
|
||||||
|
ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG, "I2S enabling failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||||
|
.port = CONFIG_BSP_I2S_NUM,
|
||||||
|
.rx_handle = i2s_rx_chan,
|
||||||
|
.tx_handle = i2s_tx_chan,
|
||||||
|
};
|
||||||
|
i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
||||||
|
BSP_NULL_CHECK_GOTO(i2s_data_if, err);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (i2s_tx_chan) {
|
||||||
|
i2s_del_channel(i2s_tx_chan);
|
||||||
|
}
|
||||||
|
if (i2s_rx_chan) {
|
||||||
|
i2s_del_channel(i2s_rx_chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_codec_dev_handle_t bsp_audio_codec_speaker_init(void)
|
||||||
|
{
|
||||||
|
if (i2s_data_if == NULL) {
|
||||||
|
/* Initilize I2C */
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_i2c_init());
|
||||||
|
/* Configure I2S peripheral and Power Amplifier */
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_audio_init(NULL));
|
||||||
|
}
|
||||||
|
assert(i2s_data_if);
|
||||||
|
|
||||||
|
const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio();
|
||||||
|
|
||||||
|
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||||
|
.port = BSP_I2C_NUM,
|
||||||
|
.addr = ES8311_CODEC_DEFAULT_ADDR,
|
||||||
|
.bus_handle = i2c_handle,
|
||||||
|
};
|
||||||
|
const audio_codec_ctrl_if_t *i2c_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||||
|
BSP_NULL_CHECK(i2c_ctrl_if, NULL);
|
||||||
|
|
||||||
|
esp_codec_dev_hw_gain_t gain = {
|
||||||
|
.pa_voltage = 5.0,
|
||||||
|
.codec_dac_voltage = 3.3,
|
||||||
|
};
|
||||||
|
|
||||||
|
es8311_codec_cfg_t es8311_cfg = {
|
||||||
|
.ctrl_if = i2c_ctrl_if,
|
||||||
|
.gpio_if = gpio_if,
|
||||||
|
.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
|
||||||
|
.pa_pin = BSP_POWER_AMP_IO,
|
||||||
|
.pa_reverted = false,
|
||||||
|
.master_mode = false,
|
||||||
|
.use_mclk = true,
|
||||||
|
.digital_mic = false,
|
||||||
|
.invert_mclk = false,
|
||||||
|
.invert_sclk = false,
|
||||||
|
.hw_gain = gain,
|
||||||
|
};
|
||||||
|
const audio_codec_if_t *es8311_dev = es8311_codec_new(&es8311_cfg);
|
||||||
|
BSP_NULL_CHECK(es8311_dev, NULL);
|
||||||
|
|
||||||
|
esp_codec_dev_cfg_t codec_dev_cfg = {
|
||||||
|
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||||
|
.codec_if = es8311_dev,
|
||||||
|
.data_if = i2s_data_if,
|
||||||
|
};
|
||||||
|
return esp_codec_dev_new(&codec_dev_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_codec_dev_handle_t bsp_audio_codec_microphone_init(void)
|
||||||
|
{
|
||||||
|
if (i2s_data_if == NULL) {
|
||||||
|
/* Initilize I2C */
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_i2c_init());
|
||||||
|
/* Configure I2S peripheral and Power Amplifier */
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_audio_init(NULL));
|
||||||
|
}
|
||||||
|
assert(i2s_data_if);
|
||||||
|
|
||||||
|
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||||
|
.port = BSP_I2C_NUM,
|
||||||
|
.addr = BSP_ES7210_CODEC_ADDR,
|
||||||
|
.bus_handle = i2c_handle,
|
||||||
|
};
|
||||||
|
const audio_codec_ctrl_if_t *i2c_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||||
|
BSP_NULL_CHECK(i2c_ctrl_if, NULL);
|
||||||
|
|
||||||
|
es7210_codec_cfg_t es7210_cfg = {
|
||||||
|
.ctrl_if = i2c_ctrl_if,
|
||||||
|
};
|
||||||
|
const audio_codec_if_t *es7210_dev = es7210_codec_new(&es7210_cfg);
|
||||||
|
BSP_NULL_CHECK(es7210_dev, NULL);
|
||||||
|
|
||||||
|
esp_codec_dev_cfg_t codec_es7210_dev_cfg = {
|
||||||
|
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||||
|
.codec_if = es7210_dev,
|
||||||
|
.data_if = i2s_data_if,
|
||||||
|
};
|
||||||
|
return esp_codec_dev_new(&codec_es7210_dev_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bit number used to represent command and parameter
|
||||||
|
#define LCD_LEDC_CH CONFIG_BSP_DISPLAY_BRIGHTNESS_LEDC_CH
|
||||||
|
|
||||||
|
esp_err_t bsp_display_brightness_init(void)
|
||||||
|
{
|
||||||
|
return bsp_i2c_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_display_brightness_set(int brightness_percent)
|
||||||
|
{
|
||||||
|
if (brightness_percent > 100) {
|
||||||
|
brightness_percent = 100;
|
||||||
|
} else if (brightness_percent < 0) {
|
||||||
|
brightness_percent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
|
||||||
|
|
||||||
|
uint8_t data = (uint8_t)(255 * brightness_percent / 100);
|
||||||
|
uint8_t data_to_send[2] = {0x96, data};
|
||||||
|
|
||||||
|
i2c_device_config_t i2c_dev_conf = {
|
||||||
|
.scl_speed_hz = 100 * 1000,
|
||||||
|
.device_address = 0x45,
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c_master_dev_handle_t dev_handle = NULL;
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(i2c_master_bus_add_device(i2c_handle, &i2c_dev_conf, &dev_handle));
|
||||||
|
esp_err_t ret = i2c_master_transmit(dev_handle, data_to_send, sizeof(data_to_send), 50);
|
||||||
|
i2c_master_bus_rm_device(dev_handle);
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(ret);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_display_backlight_off(void)
|
||||||
|
{
|
||||||
|
return bsp_display_brightness_set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_display_backlight_on(void)
|
||||||
|
{
|
||||||
|
return bsp_display_brightness_set(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t bsp_enable_dsi_phy_power(void)
|
||||||
|
{
|
||||||
|
#if BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0
|
||||||
|
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
|
||||||
|
static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
|
||||||
|
esp_ldo_channel_config_t ldo_cfg = {
|
||||||
|
.chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN,
|
||||||
|
.voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||||
|
};
|
||||||
|
ESP_RETURN_ON_ERROR(esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan), TAG, "Acquire LDO channel for DPHY failed");
|
||||||
|
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
|
||||||
|
#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel, esp_lcd_panel_io_handle_t *ret_io)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
bsp_lcd_handles_t handles;
|
||||||
|
ret = bsp_display_new_with_handles(config, &handles);
|
||||||
|
|
||||||
|
*ret_panel = handles.panel;
|
||||||
|
*ret_io = handles.io;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_display_new_with_handles(const bsp_display_config_t *config, bsp_lcd_handles_t *ret_handles)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(bsp_display_brightness_init(), TAG, "Brightness init failed");
|
||||||
|
ESP_RETURN_ON_ERROR(bsp_enable_dsi_phy_power(), TAG, "DSI PHY power failed");
|
||||||
|
ESP_RETURN_ON_ERROR(bsp_enable_ldo_vo4(), TAG, "DSI PHY power failed");
|
||||||
|
|
||||||
|
/* create MIPI DSI bus first, it will initialize the DSI PHY as well */
|
||||||
|
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
|
||||||
|
esp_lcd_dsi_bus_config_t bus_config = {
|
||||||
|
.bus_id = 0,
|
||||||
|
.num_data_lanes = BSP_LCD_MIPI_DSI_LANE_NUM,
|
||||||
|
.phy_clk_src = 0,
|
||||||
|
.lane_bit_rate_mbps = BSP_LCD_MIPI_DSI_LANE_BITRATE_MBPS,
|
||||||
|
};
|
||||||
|
ESP_RETURN_ON_ERROR(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus), TAG, "New DSI bus init failed");
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
|
||||||
|
// we use DBI interface to send LCD commands and parameters
|
||||||
|
esp_lcd_panel_io_handle_t io;
|
||||||
|
esp_lcd_dbi_io_config_t dbi_config = {
|
||||||
|
.virtual_channel = 0,
|
||||||
|
.lcd_cmd_bits = 8, // according to the LCD ILI9881C spec
|
||||||
|
.lcd_param_bits = 8, // according to the LCD ILI9881C spec
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io), err, TAG, "New panel IO failed");
|
||||||
|
|
||||||
|
esp_lcd_panel_handle_t disp_panel = NULL;
|
||||||
|
ESP_LOGI(TAG, "Install Waveshare ESP32-P4 4-inch ST7703 LCD control panel");
|
||||||
|
#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
esp_lcd_dpi_panel_config_t dpi_config = ST7703_720_720_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB888);
|
||||||
|
#else
|
||||||
|
esp_lcd_dpi_panel_config_t dpi_config = ST7703_720_720_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565);
|
||||||
|
#endif
|
||||||
|
dpi_config.num_fbs = CONFIG_BSP_LCD_DPI_BUFFER_NUMS;
|
||||||
|
|
||||||
|
st7703_vendor_config_t vendor_config = {
|
||||||
|
.mipi_config = {
|
||||||
|
.dsi_bus = mipi_dsi_bus,
|
||||||
|
.dpi_config = &dpi_config,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||||
|
#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
.bits_per_pixel = 24,
|
||||||
|
#else
|
||||||
|
.bits_per_pixel = 16,
|
||||||
|
#endif
|
||||||
|
.rgb_ele_order = BSP_LCD_COLOR_SPACE,
|
||||||
|
.reset_gpio_num = BSP_LCD_RST,
|
||||||
|
.vendor_config = &vendor_config,
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7703(io, &lcd_dev_config, &disp_panel), err, TAG, "New LCD panel Waveshare failed");
|
||||||
|
ESP_GOTO_ON_ERROR(esp_lcd_panel_reset(disp_panel), err, TAG, "LCD panel reset failed");
|
||||||
|
ESP_GOTO_ON_ERROR(esp_lcd_panel_init(disp_panel), err, TAG, "LCD panel init failed");
|
||||||
|
ESP_GOTO_ON_ERROR(esp_lcd_panel_disp_on_off(disp_panel, true), err, TAG, "LCD panel display on failed");
|
||||||
|
|
||||||
|
/* Return all handles */
|
||||||
|
ret_handles->io = io;
|
||||||
|
ret_handles->mipi_dsi_bus = mipi_dsi_bus;
|
||||||
|
ret_handles->panel = disp_panel;
|
||||||
|
ret_handles->control = NULL;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Display initialized");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (disp_panel) {
|
||||||
|
esp_lcd_panel_del(disp_panel);
|
||||||
|
}
|
||||||
|
if (io) {
|
||||||
|
esp_lcd_panel_io_del(io);
|
||||||
|
}
|
||||||
|
if (mipi_dsi_bus) {
|
||||||
|
esp_lcd_del_dsi_bus(mipi_dsi_bus);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch)
|
||||||
|
{
|
||||||
|
/* Initilize I2C */
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(bsp_i2c_init());
|
||||||
|
|
||||||
|
/* Initialize touch */
|
||||||
|
const esp_lcd_touch_config_t tp_cfg = {
|
||||||
|
#if CONFIG_BSP_LCD_TYPE_480_640_2_8_INCH || CONFIG_BSP_LCD_TYPE_480_800_4_INCH
|
||||||
|
.x_max = BSP_LCD_V_RES,
|
||||||
|
.y_max = BSP_LCD_H_RES,
|
||||||
|
#else
|
||||||
|
.x_max = BSP_LCD_H_RES,
|
||||||
|
.y_max = BSP_LCD_V_RES,
|
||||||
|
#endif
|
||||||
|
.rst_gpio_num = BSP_LCD_TOUCH_RST, // Shared with LCD reset
|
||||||
|
.int_gpio_num = BSP_LCD_TOUCH_INT,
|
||||||
|
.levels = {
|
||||||
|
.reset = 0,
|
||||||
|
.interrupt = 0,
|
||||||
|
},
|
||||||
|
.flags = {
|
||||||
|
.swap_xy = 0,
|
||||||
|
.mirror_x = 0,
|
||||||
|
.mirror_y = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
|
||||||
|
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
|
||||||
|
tp_io_config.scl_speed_hz = CONFIG_BSP_I2C_CLK_SPEED_HZ;
|
||||||
|
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle), TAG, "");
|
||||||
|
return esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, ret_touch);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
static lv_display_t *bsp_display_lcd_init(const bsp_display_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
assert(cfg != NULL);
|
||||||
|
bsp_lcd_handles_t lcd_panels;
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_display_new_with_handles(NULL, &lcd_panels));
|
||||||
|
|
||||||
|
/* Add LCD screen */
|
||||||
|
ESP_LOGD(TAG, "Add LCD screen");
|
||||||
|
const lvgl_port_display_cfg_t disp_cfg = {
|
||||||
|
.io_handle = lcd_panels.io,
|
||||||
|
.panel_handle = lcd_panels.panel,
|
||||||
|
.control_handle = lcd_panels.control,
|
||||||
|
.buffer_size = cfg->buffer_size,
|
||||||
|
.double_buffer = cfg->double_buffer,
|
||||||
|
.hres = BSP_LCD_H_RES,
|
||||||
|
.vres = BSP_LCD_V_RES,
|
||||||
|
.monochrome = false,
|
||||||
|
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
|
||||||
|
.rotation = {
|
||||||
|
.swap_xy = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
},
|
||||||
|
#if LVGL_VERSION_MAJOR >= 9
|
||||||
|
#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
.color_format = LV_COLOR_FORMAT_RGB888,
|
||||||
|
#else
|
||||||
|
.color_format = LV_COLOR_FORMAT_RGB565,
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
.flags = {
|
||||||
|
.buff_dma = cfg->flags.buff_dma,
|
||||||
|
.buff_spiram = cfg->flags.buff_spiram,
|
||||||
|
#if LVGL_VERSION_MAJOR >= 9
|
||||||
|
.swap_bytes = (BSP_LCD_BIGENDIAN ? true : false),
|
||||||
|
#endif
|
||||||
|
#if CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR
|
||||||
|
.sw_rotate = false, /* Avoid tearing is not supported for SW rotation */
|
||||||
|
#else
|
||||||
|
.sw_rotate = cfg->flags.sw_rotate, /* Only SW rotation is supported for 90° and 270° */
|
||||||
|
#endif
|
||||||
|
#if CONFIG_BSP_DISPLAY_LVGL_FULL_REFRESH
|
||||||
|
.full_refresh = true,
|
||||||
|
#elif CONFIG_BSP_DISPLAY_LVGL_DIRECT_MODE
|
||||||
|
.direct_mode = true,
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const lvgl_port_display_dsi_cfg_t dpi_cfg = {
|
||||||
|
.flags = {
|
||||||
|
#if CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR
|
||||||
|
.avoid_tearing = true,
|
||||||
|
#else
|
||||||
|
.avoid_tearing = false,
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static lv_indev_t *bsp_display_indev_init(lv_display_t *disp)
|
||||||
|
{
|
||||||
|
esp_lcd_touch_handle_t tp;
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_touch_new(NULL, &tp));
|
||||||
|
assert(tp);
|
||||||
|
|
||||||
|
/* Add touch input (for selected screen) */
|
||||||
|
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||||
|
.disp = disp,
|
||||||
|
.handle = tp,
|
||||||
|
};
|
||||||
|
|
||||||
|
return lvgl_port_add_touch(&touch_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_t *bsp_display_start(void)
|
||||||
|
{
|
||||||
|
bsp_display_cfg_t cfg = {
|
||||||
|
.lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG(),
|
||||||
|
.buffer_size = BSP_LCD_DRAW_BUFF_SIZE,
|
||||||
|
.double_buffer = BSP_LCD_DRAW_BUFF_DOUBLE,
|
||||||
|
.flags = {
|
||||||
|
#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
.buff_dma = false,
|
||||||
|
#else
|
||||||
|
.buff_dma = true,
|
||||||
|
#endif
|
||||||
|
.buff_spiram = false,
|
||||||
|
.sw_rotate = true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return bsp_display_start_with_config(&cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_t *bsp_display_start_with_config(const bsp_display_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
lv_display_t *disp;
|
||||||
|
|
||||||
|
assert(cfg != NULL);
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(lvgl_port_init(&cfg->lvgl_port_cfg));
|
||||||
|
|
||||||
|
BSP_ERROR_CHECK_RETURN_NULL(bsp_display_brightness_init());
|
||||||
|
|
||||||
|
BSP_NULL_CHECK(disp = bsp_display_lcd_init(cfg), NULL);
|
||||||
|
|
||||||
|
BSP_NULL_CHECK(disp_indev = bsp_display_indev_init(disp), NULL);
|
||||||
|
|
||||||
|
return disp;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_indev_t *bsp_display_get_input_dev(void)
|
||||||
|
{
|
||||||
|
return disp_indev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bsp_display_rotate(lv_display_t *disp, lv_disp_rotation_t rotation)
|
||||||
|
{
|
||||||
|
lv_disp_set_rotation(disp, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bsp_display_lock(uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
return lvgl_port_lock(timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bsp_display_unlock(void)
|
||||||
|
{
|
||||||
|
lvgl_port_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
|
||||||
|
static void usb_lib_task(void *arg)
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
// Start handling system events
|
||||||
|
uint32_t event_flags;
|
||||||
|
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
|
||||||
|
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
|
||||||
|
ESP_ERROR_CHECK(usb_host_device_free_all());
|
||||||
|
}
|
||||||
|
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
|
||||||
|
ESP_LOGI(TAG, "USB: All devices freed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_usb_host_start(bsp_usb_host_power_mode_t mode, bool limit_500mA)
|
||||||
|
{
|
||||||
|
//Install USB Host driver. Should only be called once in entire application
|
||||||
|
ESP_LOGI(TAG, "Installing USB Host");
|
||||||
|
const usb_host_config_t host_config = {
|
||||||
|
.skip_phy_setup = false,
|
||||||
|
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||||||
|
};
|
||||||
|
BSP_ERROR_CHECK_RETURN_ERR(usb_host_install(&host_config));
|
||||||
|
|
||||||
|
// Create a task that will handle USB library events
|
||||||
|
if (xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, &usb_host_task) != pdTRUE) {
|
||||||
|
ESP_LOGE(TAG, "Creating USB host lib task failed");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t bsp_usb_host_stop(void)
|
||||||
|
{
|
||||||
|
usb_host_uninstall();
|
||||||
|
if (usb_host_task) {
|
||||||
|
vTaskSuspend(usb_host_task);
|
||||||
|
vTaskDelete(usb_host_task);
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
18
components/esp32_p4_wifi6_touch_lcd_4b/idf_component.yml
Normal file
18
components/esp32_p4_wifi6_touch_lcd_4b/idf_component.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
dependencies:
|
||||||
|
esp_codec_dev:
|
||||||
|
public: true
|
||||||
|
version: "~1.5"
|
||||||
|
esp_lcd_touch_gt911: ^1
|
||||||
|
espressif/esp_lvgl_port:
|
||||||
|
public: true
|
||||||
|
version: ^2
|
||||||
|
lvgl/lvgl: '>=8,<10'
|
||||||
|
waveshare/esp_lcd_st7703: "1.0.1"
|
||||||
|
description: Based on ESP32-P4 chip, Waveshare ESP32-P4-86-Panel-ETH-2RO/4-inch 720x720 panel support
|
||||||
|
repository: git://github.com/WaveshareCloud/Waveshare-ESP32-components.git
|
||||||
|
tags:
|
||||||
|
- bsp
|
||||||
|
targets:
|
||||||
|
- esp32p4
|
||||||
|
url: https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-3.4c.htm
|
||||||
|
version: 1.0.3
|
||||||
10
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/config.h
Normal file
10
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/config.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* BSP configuration
|
||||||
|
**************************************************************************************************/
|
||||||
|
// By default, this BSP is shipped with LVGL graphical library. Enabling this option will exclude it.
|
||||||
|
// If you want to use BSP without LVGL, select BSP version with 'noglib' suffix.
|
||||||
|
#if !defined(BSP_CONFIG_NO_GRAPHIC_LIB) // Check if the symbol is not coming from compiler definitions (-D...)
|
||||||
|
#define BSP_CONFIG_NO_GRAPHIC_LIB (0)
|
||||||
|
#endif
|
||||||
154
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/display.h
Normal file
154
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/display.h
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "esp_lcd_types.h"
|
||||||
|
#include "esp_lcd_mipi_dsi.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
/* LCD color formats */
|
||||||
|
#define ESP_LCD_COLOR_FORMAT_RGB565 (1)
|
||||||
|
#define ESP_LCD_COLOR_FORMAT_RGB888 (2)
|
||||||
|
|
||||||
|
/* LCD display color format */
|
||||||
|
#if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
|
||||||
|
#define BSP_LCD_COLOR_FORMAT (ESP_LCD_COLOR_FORMAT_RGB888)
|
||||||
|
#else
|
||||||
|
#define BSP_LCD_COLOR_FORMAT (ESP_LCD_COLOR_FORMAT_RGB565)
|
||||||
|
#endif
|
||||||
|
/* LCD display color bytes endianess */
|
||||||
|
#define BSP_LCD_BIGENDIAN (0)
|
||||||
|
/* LCD display color bits */
|
||||||
|
#define BSP_LCD_BITS_PER_PIXEL (16)
|
||||||
|
/* LCD display color space */
|
||||||
|
#define BSP_LCD_COLOR_SPACE (LCD_RGB_ELEMENT_ORDER_RGB)
|
||||||
|
|
||||||
|
#define BSP_LCD_H_RES (720)
|
||||||
|
#define BSP_LCD_V_RES (720)
|
||||||
|
#define BSP_LCD_MIPI_DSI_LANE_BITRATE_MBPS (1200)
|
||||||
|
|
||||||
|
#define BSP_LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes
|
||||||
|
#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3) // LDO_VO3 is connected to VDD_MIPI_DPHY
|
||||||
|
#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BSP display configuration structure
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int dummy;
|
||||||
|
} bsp_display_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BSP display return handles
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
esp_lcd_dsi_bus_handle_t mipi_dsi_bus; /*!< MIPI DSI bus handle */
|
||||||
|
esp_lcd_panel_io_handle_t io; /*!< ESP LCD IO handle */
|
||||||
|
esp_lcd_panel_handle_t panel; /*!< ESP LCD panel (color) handle */
|
||||||
|
esp_lcd_panel_handle_t control; /*!< ESP LCD panel (control) handle */
|
||||||
|
} bsp_lcd_handles_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create new display panel
|
||||||
|
*
|
||||||
|
* For maximum flexibility, this function performs only reset and initialization of the display.
|
||||||
|
* You must turn on the display explicitly by calling esp_lcd_panel_disp_on_off().
|
||||||
|
* The display's backlight is not turned on either. You can use bsp_display_backlight_on/off(),
|
||||||
|
* bsp_display_brightness_set() (on supported boards) or implement your own backlight control.
|
||||||
|
*
|
||||||
|
* If you want to free resources allocated by this function, you can use esp_lcd API, ie.:
|
||||||
|
*
|
||||||
|
* \code{.c}
|
||||||
|
* esp_lcd_panel_del(panel);
|
||||||
|
* esp_lcd_panel_io_del(io);
|
||||||
|
* esp_lcd_del_dsi_bus(mipi_dsi_bus);
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* @param[in] config display configuration
|
||||||
|
* @param[out] ret_panel esp_lcd panel handle
|
||||||
|
* @param[out] ret_io esp_lcd IO handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - Else esp_lcd failure
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel, esp_lcd_panel_io_handle_t *ret_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create new display panel
|
||||||
|
*
|
||||||
|
* For maximum flexibility, this function performs only reset and initialization of the display.
|
||||||
|
* You must turn on the display explicitly by calling esp_lcd_panel_disp_on_off().
|
||||||
|
* The display's backlight is not turned on either. You can use bsp_display_backlight_on/off(),
|
||||||
|
* bsp_display_brightness_set() (on supported boards) or implement your own backlight control.
|
||||||
|
*
|
||||||
|
* If you want to free resources allocated by this function, you can use esp_lcd API, ie.:
|
||||||
|
*
|
||||||
|
* \code{.c}
|
||||||
|
* esp_lcd_panel_del(panel);
|
||||||
|
* esp_lcd_panel_del(control);
|
||||||
|
* esp_lcd_panel_io_del(io);
|
||||||
|
* esp_lcd_del_dsi_bus(mipi_dsi_bus);
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* @param[in] config display configuration
|
||||||
|
* @param[out] ret_handles all esp_lcd handles in one structure
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - Else esp_lcd failure
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_new_with_handles(const bsp_display_config_t *config, bsp_lcd_handles_t *ret_handles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize display's brightness
|
||||||
|
*
|
||||||
|
* Brightness is controlled with PWM signal to a pin controlling backlight.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_brightness_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set display's brightness
|
||||||
|
*
|
||||||
|
* Brightness is controlled with PWM signal to a pin controlling backlight.
|
||||||
|
* Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new()
|
||||||
|
*
|
||||||
|
* @param[in] brightness_percent Brightness in [%]
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_brightness_set(int brightness_percent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Turn on display backlight
|
||||||
|
*
|
||||||
|
* Brightness is controlled with PWM signal to a pin controlling backlight.
|
||||||
|
* Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new()
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_backlight_on(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Turn off display backlight
|
||||||
|
*
|
||||||
|
* Brightness is controlled with PWM signal to a pin controlling backlight.
|
||||||
|
* Brightness must be already initialized by calling bsp_display_brightness_init() or bsp_display_new()
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_display_backlight_off(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "bsp/esp32_p4_wifi6_touch_lcd_4b.h"
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "driver/sdmmc_host.h"
|
||||||
|
#include "driver/i2s_std.h"
|
||||||
|
#include "bsp/config.h"
|
||||||
|
#include "bsp/display.h"
|
||||||
|
#include "esp_codec_dev.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "esp_lvgl_port.h"
|
||||||
|
#endif // BSP_CONFIG_NO_GRAPHIC_LIB == 0
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* BSP Capabilities
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
#define BSP_CAPS_DISPLAY 1
|
||||||
|
#define BSP_CAPS_TOUCH 1
|
||||||
|
#define BSP_CAPS_BUTTONS 0
|
||||||
|
#define BSP_CAPS_AUDIO 1
|
||||||
|
#define BSP_CAPS_AUDIO_SPEAKER 1
|
||||||
|
#define BSP_CAPS_AUDIO_MIC 1
|
||||||
|
#define BSP_CAPS_SDCARD 1
|
||||||
|
#define BSP_CAPS_IMU 0
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* ESP-BOX pinout
|
||||||
|
**************************************************************************************************/
|
||||||
|
/* I2C */
|
||||||
|
#define BSP_I2C_SCL (GPIO_NUM_8)
|
||||||
|
#define BSP_I2C_SDA (GPIO_NUM_7)
|
||||||
|
|
||||||
|
/* Audio */
|
||||||
|
#define BSP_I2S_SCLK (GPIO_NUM_12)
|
||||||
|
#define BSP_I2S_MCLK (GPIO_NUM_13)
|
||||||
|
#define BSP_I2S_LCLK (GPIO_NUM_10)
|
||||||
|
#define BSP_I2S_DOUT (GPIO_NUM_9)
|
||||||
|
#define BSP_I2S_DSIN (GPIO_NUM_11)
|
||||||
|
#define BSP_POWER_AMP_IO (GPIO_NUM_53)
|
||||||
|
|
||||||
|
#define BSP_LCD_BACKLIGHT (GPIO_NUM_NC)
|
||||||
|
#define BSP_LCD_RST (GPIO_NUM_NC)
|
||||||
|
#define BSP_LCD_TOUCH_RST (GPIO_NUM_NC)
|
||||||
|
#define BSP_LCD_TOUCH_INT (GPIO_NUM_NC)
|
||||||
|
|
||||||
|
/* uSD card */
|
||||||
|
#define BSP_SD_D0 (GPIO_NUM_39)
|
||||||
|
#define BSP_SD_D1 (GPIO_NUM_40)
|
||||||
|
#define BSP_SD_D2 (GPIO_NUM_41)
|
||||||
|
#define BSP_SD_D3 (GPIO_NUM_42)
|
||||||
|
#define BSP_SD_CMD (GPIO_NUM_44)
|
||||||
|
#define BSP_SD_CLK (GPIO_NUM_43)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* I2C interface
|
||||||
|
*
|
||||||
|
* There are multiple devices connected to I2C peripheral:
|
||||||
|
* - Codec ES8311 (configuration only)
|
||||||
|
* - LCD Touch controller
|
||||||
|
**************************************************************************************************/
|
||||||
|
#define BSP_I2C_NUM CONFIG_BSP_I2C_NUM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Init I2C driver
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG I2C parameter error
|
||||||
|
* - ESP_FAIL I2C driver installation error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_i2c_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinit I2C driver and free its resources
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG I2C parameter error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_i2c_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get I2C driver handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - I2C handle
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
i2c_master_bus_handle_t bsp_i2c_get_handle(void);
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* I2S audio interface
|
||||||
|
*
|
||||||
|
* There are two devices connected to the I2S peripheral:
|
||||||
|
* - Codec ES8311 for output(playback) and input(recording) path
|
||||||
|
*
|
||||||
|
* For speaker initialization use bsp_audio_codec_speaker_init() which is inside initialize I2S with bsp_audio_init().
|
||||||
|
* For microphone initialization use bsp_audio_codec_microphone_init() which is inside initialize I2S with bsp_audio_init().
|
||||||
|
* After speaker or microphone initialization, use functions from esp_codec_dev for play/record audio.
|
||||||
|
* Example audio play:
|
||||||
|
* \code{.c}
|
||||||
|
* esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME);
|
||||||
|
* esp_codec_dev_open(spk_codec_dev, &fs);
|
||||||
|
* esp_codec_dev_write(spk_codec_dev, wav_bytes, bytes_read_from_spiffs);
|
||||||
|
* esp_codec_dev_close(spk_codec_dev);
|
||||||
|
* \endcode
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Init audio
|
||||||
|
*
|
||||||
|
* @note There is no deinit audio function. Users can free audio resources by calling i2s_del_channel()
|
||||||
|
* @warning The type of i2s_config param is depending on IDF version.
|
||||||
|
* @param[in] i2s_config I2S configuration. Pass NULL to use default values (Mono, duplex, 16bit, 22050 Hz)
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_NOT_SUPPORTED The communication mode is not supported on the current chip
|
||||||
|
* - ESP_ERR_INVALID_ARG NULL pointer or invalid configuration
|
||||||
|
* - ESP_ERR_NOT_FOUND No available I2S channel found
|
||||||
|
* - ESP_ERR_NO_MEM No memory for storing the channel information
|
||||||
|
* - ESP_ERR_INVALID_STATE This channel has not initialized or already started
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_audio_init(const i2s_std_config_t *i2s_config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize speaker codec device
|
||||||
|
*
|
||||||
|
* @return Pointer to codec device handle or NULL when error occurred
|
||||||
|
*/
|
||||||
|
esp_codec_dev_handle_t bsp_audio_codec_speaker_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize microphone codec device
|
||||||
|
*
|
||||||
|
* @return Pointer to codec device handle or NULL when error occurred
|
||||||
|
*/
|
||||||
|
esp_codec_dev_handle_t bsp_audio_codec_microphone_init(void);
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* SPIFFS
|
||||||
|
*
|
||||||
|
* After mounting the SPIFFS, it can be accessed with stdio functions ie.:
|
||||||
|
* \code{.c}
|
||||||
|
* FILE* f = fopen(BSP_SPIFFS_MOUNT_POINT"/hello.txt", "w");
|
||||||
|
* fprintf(f, "Hello World!\n");
|
||||||
|
* fclose(f);
|
||||||
|
* \endcode
|
||||||
|
**************************************************************************************************/
|
||||||
|
#define BSP_SPIFFS_MOUNT_POINT CONFIG_BSP_SPIFFS_MOUNT_POINT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mount SPIFFS to virtual file system
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success
|
||||||
|
* - ESP_ERR_INVALID_STATE if esp_vfs_spiffs_register was already called
|
||||||
|
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||||
|
* - ESP_FAIL if partition can not be mounted
|
||||||
|
* - other error codes
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_spiffs_mount(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmount SPIFFS from virtual file system
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success
|
||||||
|
* - ESP_ERR_NOT_FOUND if the partition table does not contain SPIFFS partition with given label
|
||||||
|
* - ESP_ERR_INVALID_STATE if esp_vfs_spiffs_unregister was already called
|
||||||
|
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||||
|
* - ESP_FAIL if partition can not be mounted
|
||||||
|
* - other error codes
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_spiffs_unmount(void);
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* uSD card
|
||||||
|
*
|
||||||
|
* After mounting the uSD card, it can be accessed with stdio functions ie.:
|
||||||
|
* \code{.c}
|
||||||
|
* FILE* f = fopen(BSP_MOUNT_POINT"/hello.txt", "w");
|
||||||
|
* fprintf(f, "Hello %s!\n", bsp_sdcard->cid.name);
|
||||||
|
* fclose(f);
|
||||||
|
* \endcode
|
||||||
|
**************************************************************************************************/
|
||||||
|
#define BSP_SD_MOUNT_POINT CONFIG_BSP_SD_MOUNT_POINT
|
||||||
|
extern sdmmc_card_t *bsp_sdcard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mount microSD card to virtual file system
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success
|
||||||
|
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
|
||||||
|
* - ESP_ERR_NO_MEM if memory cannot be allocated
|
||||||
|
* - ESP_FAIL if partition cannot be mounted
|
||||||
|
* - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_sdcard_mount(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmount microSD card from virtual file system
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success
|
||||||
|
* - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
|
||||||
|
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount was already called
|
||||||
|
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||||
|
* - ESP_FAIL if partition can not be mounted
|
||||||
|
* - other error codes from wear levelling library, SPI flash driver, or FATFS drivers
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_sdcard_unmount(void);
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* LCD interface
|
||||||
|
*
|
||||||
|
* ESP-BOX is shipped with 2.4inch ST7789 display controller.
|
||||||
|
* It features 16-bit colors, 320x240 resolution and capacitive touch controller.
|
||||||
|
*
|
||||||
|
* LVGL is used as graphics library. LVGL is NOT thread safe, therefore the user must take LVGL mutex
|
||||||
|
* by calling bsp_display_lock() before calling and LVGL API (lv_...) and then give the mutex with
|
||||||
|
* bsp_display_unlock().
|
||||||
|
*
|
||||||
|
* Display's backlight must be enabled explicitly by calling bsp_display_backlight_on()
|
||||||
|
**************************************************************************************************/
|
||||||
|
#define BSP_LCD_PIXEL_CLOCK_MHZ (80)
|
||||||
|
|
||||||
|
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
|
||||||
|
|
||||||
|
#define BSP_LCD_DRAW_BUFF_SIZE (BSP_LCD_H_RES * 50) // Frame buffer size in pixels
|
||||||
|
#define BSP_LCD_DRAW_BUFF_DOUBLE (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BSP display configuration structure
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
lvgl_port_cfg_t lvgl_port_cfg; /*!< LVGL port configuration */
|
||||||
|
uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */
|
||||||
|
bool double_buffer; /*!< True, if should be allocated two buffers */
|
||||||
|
struct {
|
||||||
|
unsigned int buff_dma: 1; /*!< Allocated LVGL buffer will be DMA capable */
|
||||||
|
unsigned int buff_spiram: 1; /*!< Allocated LVGL buffer will be in PSRAM */
|
||||||
|
unsigned int sw_rotate: 1; /*!< Use software rotation (slower), The feature is unavailable under avoid-tear mode */
|
||||||
|
} flags;
|
||||||
|
} bsp_display_cfg_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize display
|
||||||
|
*
|
||||||
|
* This function initializes SPI, display controller and starts LVGL handling task.
|
||||||
|
* LCD backlight must be enabled separately by calling bsp_display_brightness_set()
|
||||||
|
*
|
||||||
|
* @return Pointer to LVGL display or NULL when error occured
|
||||||
|
*/
|
||||||
|
lv_display_t *bsp_display_start(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize display
|
||||||
|
*
|
||||||
|
* This function initializes SPI, display controller and starts LVGL handling task.
|
||||||
|
* LCD backlight must be enabled separately by calling bsp_display_brightness_set()
|
||||||
|
*
|
||||||
|
* @param cfg display configuration
|
||||||
|
*
|
||||||
|
* @return Pointer to LVGL display or NULL when error occured
|
||||||
|
*/
|
||||||
|
lv_display_t *bsp_display_start_with_config(const bsp_display_cfg_t *cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get pointer to input device (touch, buttons, ...)
|
||||||
|
*
|
||||||
|
* @note The LVGL input device is initialized in bsp_display_start() function.
|
||||||
|
*
|
||||||
|
* @return Pointer to LVGL input device or NULL when not initialized
|
||||||
|
*/
|
||||||
|
lv_indev_t *bsp_display_get_input_dev(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Take LVGL mutex
|
||||||
|
*
|
||||||
|
* @param timeout_ms Timeout in [ms]. 0 will block indefinitely.
|
||||||
|
* @return true Mutex was taken
|
||||||
|
* @return false Mutex was NOT taken
|
||||||
|
*/
|
||||||
|
bool bsp_display_lock(uint32_t timeout_ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Give LVGL mutex
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void bsp_display_unlock(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rotate screen
|
||||||
|
*
|
||||||
|
* Display must be already initialized by calling bsp_display_start()
|
||||||
|
*
|
||||||
|
* @param[in] disp Pointer to LVGL display
|
||||||
|
* @param[in] rotation Angle of the display rotation
|
||||||
|
*/
|
||||||
|
void bsp_display_rotate(lv_display_t *disp, lv_disp_rotation_t rotation);
|
||||||
|
#endif // BSP_CONFIG_NO_GRAPHIC_LIB == 0
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
*
|
||||||
|
* USB
|
||||||
|
*
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Power modes of USB Host connector
|
||||||
|
*/
|
||||||
|
typedef enum bsp_usb_host_power_mode_t {
|
||||||
|
BSP_USB_HOST_POWER_MODE_USB_DEV, //!< Power from USB DEV port
|
||||||
|
} bsp_usb_host_power_mode_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start USB host
|
||||||
|
*
|
||||||
|
* This is a one-stop-shop function that will configure the board for USB Host mode
|
||||||
|
* and start USB Host library
|
||||||
|
*
|
||||||
|
* @param[in] mode USB Host connector power mode (Not used on this board)
|
||||||
|
* @param[in] limit_500mA Limit output current to 500mA (Not used on this board)
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
* - ESP_ERR_NO_MEM Memory cannot be allocated
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_usb_host_start(bsp_usb_host_power_mode_t mode, bool limit_500mA);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop USB host
|
||||||
|
*
|
||||||
|
* USB Host lib will be uninstalled and power from connector removed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - ESP_ERR_INVALID_ARG Parameter error
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_usb_host_stop(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
35
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/touch.h
Normal file
35
components/esp32_p4_wifi6_touch_lcd_4b/include/bsp/touch.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "esp_lcd_touch.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BSP touch configuration structure
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
void *dummy; /*!< Prepared for future use. */
|
||||||
|
} bsp_touch_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create new touchscreen
|
||||||
|
*
|
||||||
|
* If you want to free resources allocated by this function, you can use esp_lcd_touch API, ie.:
|
||||||
|
*
|
||||||
|
* \code{.c}
|
||||||
|
* esp_lcd_touch_del(tp);
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* @param[in] config touch configuration
|
||||||
|
* @param[out] ret_touch esp_lcd_touch touchscreen handle
|
||||||
|
* @return
|
||||||
|
* - ESP_OK On success
|
||||||
|
* - Else esp_lcd_touch failure
|
||||||
|
*/
|
||||||
|
esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Assert on error, if selected in menuconfig. Otherwise return error code. */
|
||||||
|
#if CONFIG_BSP_ERROR_CHECK
|
||||||
|
#define BSP_ERROR_CHECK_RETURN_ERR(x) ESP_ERROR_CHECK(x)
|
||||||
|
#define BSP_ERROR_CHECK_RETURN_NULL(x) ESP_ERROR_CHECK(x)
|
||||||
|
#define BSP_ERROR_CHECK(x, ret) ESP_ERROR_CHECK(x)
|
||||||
|
#define BSP_NULL_CHECK(x, ret) assert(x)
|
||||||
|
#define BSP_NULL_CHECK_GOTO(x, goto_tag) assert(x)
|
||||||
|
#else
|
||||||
|
#define BSP_ERROR_CHECK_RETURN_ERR(x) do { \
|
||||||
|
esp_err_t err_rc_ = (x); \
|
||||||
|
if (unlikely(err_rc_ != ESP_OK)) { \
|
||||||
|
return err_rc_; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define BSP_ERROR_CHECK_RETURN_NULL(x) do { \
|
||||||
|
if (unlikely((x) != ESP_OK)) { \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define BSP_NULL_CHECK(x, ret) do { \
|
||||||
|
if ((x) == NULL) { \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define BSP_ERROR_CHECK(x, ret) do { \
|
||||||
|
if (unlikely((x) != ESP_OK)) { \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define BSP_NULL_CHECK_GOTO(x, goto_tag) do { \
|
||||||
|
if ((x) == NULL) { \
|
||||||
|
goto goto_tag; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
11
main/CMakeLists.txt
Normal file
11
main/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS
|
||||||
|
"app_main.c"
|
||||||
|
"card_assets.c"
|
||||||
|
"tarot_deck.c"
|
||||||
|
"tarot_logic.c"
|
||||||
|
"ui_main.c"
|
||||||
|
"ui_settings.c"
|
||||||
|
"settings.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
)
|
||||||
7
main/Kconfig.projbuild
Normal file
7
main/Kconfig.projbuild
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
menu "Tarot App"
|
||||||
|
|
||||||
|
config TAROT_START_BRIGHTNESS_FROM_NVS
|
||||||
|
bool "Apply saved brightness during startup"
|
||||||
|
default y
|
||||||
|
|
||||||
|
endmenu
|
||||||
47
main/app_main.c
Normal file
47
main/app_main.c
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "bsp/esp-bsp.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
#include "ui_main.h"
|
||||||
|
|
||||||
|
static const char *TAG = "tarot_app";
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
esp_err_t err = nvs_flash_init();
|
||||||
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
|
err = nvs_flash_init();
|
||||||
|
}
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(settings_init());
|
||||||
|
|
||||||
|
lv_display_t *display = bsp_display_start();
|
||||||
|
if (display == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Waveshare BSP failed to initialize display/touch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_TAROT_START_BRIGHTNESS_FROM_NVS
|
||||||
|
tarot_settings_t settings = settings_get();
|
||||||
|
ESP_ERROR_CHECK_WITHOUT_ABORT(bsp_display_brightness_set(settings.brightness));
|
||||||
|
#else
|
||||||
|
ESP_ERROR_CHECK_WITHOUT_ABORT(bsp_display_backlight_on());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (bsp_display_lock(1000)) {
|
||||||
|
ui_main_init();
|
||||||
|
ui_show_home();
|
||||||
|
bsp_display_unlock();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Timed out taking LVGL lock during UI startup");
|
||||||
|
}
|
||||||
|
}
|
||||||
89
main/card_assets.c
Normal file
89
main/card_assets.c
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "card_assets.h"
|
||||||
|
|
||||||
|
static lv_color_t suit_color(tarot_suit_t suit)
|
||||||
|
{
|
||||||
|
switch (suit) {
|
||||||
|
case TAROT_SUIT_WANDS:
|
||||||
|
return lv_color_hex(0xd8743f);
|
||||||
|
case TAROT_SUIT_CUPS:
|
||||||
|
return lv_color_hex(0x3b83c4);
|
||||||
|
case TAROT_SUIT_SWORDS:
|
||||||
|
return lv_color_hex(0x8a92a6);
|
||||||
|
case TAROT_SUIT_PENTACLES:
|
||||||
|
return lv_color_hex(0x5fa66a);
|
||||||
|
case TAROT_SUIT_NONE:
|
||||||
|
default:
|
||||||
|
return lv_color_hex(0xb88cff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *suit_glyph(tarot_suit_t suit)
|
||||||
|
{
|
||||||
|
switch (suit) {
|
||||||
|
case TAROT_SUIT_WANDS:
|
||||||
|
return "W";
|
||||||
|
case TAROT_SUIT_CUPS:
|
||||||
|
return "C";
|
||||||
|
case TAROT_SUIT_SWORDS:
|
||||||
|
return "S";
|
||||||
|
case TAROT_SUIT_PENTACLES:
|
||||||
|
return "P";
|
||||||
|
case TAROT_SUIT_NONE:
|
||||||
|
default:
|
||||||
|
return "M";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_t *card_assets_create_card_art(lv_obj_t *parent, const tarot_card_t *card, bool reversed)
|
||||||
|
{
|
||||||
|
lv_obj_t *art = lv_obj_create(parent);
|
||||||
|
lv_obj_set_size(art, 132, 184);
|
||||||
|
lv_obj_clear_flag(art, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_set_style_radius(art, 8, 0);
|
||||||
|
lv_obj_set_style_border_width(art, 2, 0);
|
||||||
|
if (card == NULL) {
|
||||||
|
lv_obj_set_style_border_color(art, lv_color_hex(0xb88cff), 0);
|
||||||
|
lv_obj_set_style_bg_color(art, lv_color_hex(0xeee7d9), 0);
|
||||||
|
lv_obj_t *missing = lv_label_create(art);
|
||||||
|
lv_label_set_text(missing, "Card");
|
||||||
|
lv_obj_center(missing);
|
||||||
|
return art;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_set_style_border_color(art, suit_color(card->suit), 0);
|
||||||
|
lv_obj_set_style_bg_color(art, lv_color_hex(0xeee7d9), 0);
|
||||||
|
lv_obj_set_style_pad_all(art, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *top = lv_label_create(art);
|
||||||
|
lv_label_set_text_fmt(top, "%02u", card->id);
|
||||||
|
lv_obj_set_style_text_color(top, lv_color_hex(0x30261f), 0);
|
||||||
|
lv_obj_align(top, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||||
|
|
||||||
|
lv_obj_t *arcana = lv_label_create(art);
|
||||||
|
lv_label_set_text(arcana, card->arcana == TAROT_ARCANA_MAJOR ? "MAJOR" : suit_glyph(card->suit));
|
||||||
|
lv_obj_set_style_text_color(arcana, suit_color(card->suit), 0);
|
||||||
|
lv_obj_align(arcana, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||||
|
|
||||||
|
lv_obj_t *symbol = lv_label_create(art);
|
||||||
|
lv_label_set_text(symbol, suit_glyph(card->suit));
|
||||||
|
lv_obj_set_style_text_color(symbol, suit_color(card->suit), 0);
|
||||||
|
lv_obj_set_style_text_font(symbol, LV_FONT_DEFAULT, 0);
|
||||||
|
lv_obj_align(symbol, LV_ALIGN_CENTER, 0, -16);
|
||||||
|
|
||||||
|
lv_obj_t *name = lv_label_create(art);
|
||||||
|
lv_label_set_text(name, card->name);
|
||||||
|
lv_label_set_long_mode(name, LV_LABEL_LONG_WRAP);
|
||||||
|
lv_obj_set_width(name, 108);
|
||||||
|
lv_obj_set_style_text_align(name, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_set_style_text_color(name, lv_color_hex(0x30261f), 0);
|
||||||
|
lv_obj_align(name, LV_ALIGN_CENTER, 0, 42);
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
lv_obj_t *rev = lv_label_create(art);
|
||||||
|
lv_label_set_text(rev, "REV");
|
||||||
|
lv_obj_set_style_text_color(rev, lv_color_hex(0x8b1e2d), 0);
|
||||||
|
lv_obj_align(rev, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return art;
|
||||||
|
}
|
||||||
23
main/card_assets.h
Normal file
23
main/card_assets.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "tarot_deck.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First-pass on-device art renderer.
|
||||||
|
*
|
||||||
|
* The app no longer depends on SD card media. Card visuals are generated from
|
||||||
|
* LVGL widgets using deck metadata already compiled into firmware. Later, real
|
||||||
|
* embedded LVGL image descriptors can be mapped by card id inside this module.
|
||||||
|
*/
|
||||||
|
lv_obj_t *card_assets_create_card_art(lv_obj_t *parent, const tarot_card_t *card, bool reversed);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
2
main/idf_component.yml
Normal file
2
main/idf_component.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
lvgl/lvgl: ">=9,<10"
|
||||||
129
main/settings.c
Normal file
129
main/settings.c
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
static const char *TAG = "settings";
|
||||||
|
static const char *NVS_NAMESPACE = "tarot";
|
||||||
|
|
||||||
|
static tarot_settings_t s_settings;
|
||||||
|
|
||||||
|
void settings_set_defaults(tarot_settings_t *settings)
|
||||||
|
{
|
||||||
|
if (settings == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings->reversed_cards_enabled = true;
|
||||||
|
settings->save_readings_enabled = false;
|
||||||
|
settings->brightness = 80;
|
||||||
|
settings->theme = TAROT_THEME_DARK;
|
||||||
|
settings->selected_spread = TAROT_SPREAD_8_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t settings_init(void)
|
||||||
|
{
|
||||||
|
nvs_handle_t handle;
|
||||||
|
esp_err_t err;
|
||||||
|
uint8_t value = 0;
|
||||||
|
|
||||||
|
settings_set_defaults(&s_settings);
|
||||||
|
|
||||||
|
err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||||
|
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||||
|
ESP_LOGI(TAG, "No saved settings; using defaults");
|
||||||
|
return settings_save(&s_settings);
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nvs_get_u8(handle, "reversed", &value) == ESP_OK) {
|
||||||
|
s_settings.reversed_cards_enabled = value != 0;
|
||||||
|
}
|
||||||
|
if (nvs_get_u8(handle, "save_read", &value) == ESP_OK) {
|
||||||
|
s_settings.save_readings_enabled = value != 0;
|
||||||
|
}
|
||||||
|
if (nvs_get_u8(handle, "bright", &value) == ESP_OK) {
|
||||||
|
s_settings.brightness = value <= 100 ? value : 100;
|
||||||
|
}
|
||||||
|
if (nvs_get_u8(handle, "theme", &value) == ESP_OK && value <= TAROT_THEME_MYSTIC) {
|
||||||
|
s_settings.theme = (tarot_theme_t)value;
|
||||||
|
}
|
||||||
|
if (nvs_get_u8(handle, "spread", &value) == ESP_OK) {
|
||||||
|
s_settings.selected_spread = value == TAROT_SPREAD_3_CARD ? TAROT_SPREAD_3_CARD : TAROT_SPREAD_8_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvs_close(handle);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
tarot_settings_t settings_get(void)
|
||||||
|
{
|
||||||
|
return s_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t settings_save(const tarot_settings_t *settings)
|
||||||
|
{
|
||||||
|
nvs_handle_t handle;
|
||||||
|
esp_err_t err;
|
||||||
|
|
||||||
|
if (settings == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "nvs_open write failed: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nvs_set_u8(handle, "reversed", settings->reversed_cards_enabled ? 1 : 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
err = nvs_set_u8(handle, "save_read", settings->save_readings_enabled ? 1 : 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
err = nvs_set_u8(handle, "bright", settings->brightness);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
err = nvs_set_u8(handle, "theme", (uint8_t)settings->theme);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
err = nvs_set_u8(handle, "spread", (uint8_t)settings->selected_spread);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
err = nvs_commit(handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_settings = *settings;
|
||||||
|
nvs_close(handle);
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
ESP_LOGE(TAG, "settings_save failed: %s", esp_err_to_name(err));
|
||||||
|
nvs_close(handle);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *settings_theme_name(tarot_theme_t theme)
|
||||||
|
{
|
||||||
|
switch (theme) {
|
||||||
|
case TAROT_THEME_LIGHT:
|
||||||
|
return "Light";
|
||||||
|
case TAROT_THEME_MYSTIC:
|
||||||
|
return "Mystic";
|
||||||
|
case TAROT_THEME_DARK:
|
||||||
|
default:
|
||||||
|
return "Dark";
|
||||||
|
}
|
||||||
|
}
|
||||||
35
main/settings.h
Normal file
35
main/settings.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "tarot_logic.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TAROT_THEME_DARK = 0,
|
||||||
|
TAROT_THEME_LIGHT,
|
||||||
|
TAROT_THEME_MYSTIC,
|
||||||
|
} tarot_theme_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool reversed_cards_enabled;
|
||||||
|
bool save_readings_enabled;
|
||||||
|
uint8_t brightness;
|
||||||
|
tarot_theme_t theme;
|
||||||
|
tarot_spread_t selected_spread;
|
||||||
|
} tarot_settings_t;
|
||||||
|
|
||||||
|
esp_err_t settings_init(void);
|
||||||
|
tarot_settings_t settings_get(void);
|
||||||
|
esp_err_t settings_save(const tarot_settings_t *settings);
|
||||||
|
void settings_set_defaults(tarot_settings_t *settings);
|
||||||
|
const char *settings_theme_name(tarot_theme_t theme);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
116
main/tarot_deck.c
Normal file
116
main/tarot_deck.c
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
#include "tarot_deck.h"
|
||||||
|
|
||||||
|
const tarot_card_t tarot_deck[TAROT_DECK_SIZE] = {
|
||||||
|
{0, "The Fool", "00_fool.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Beginnings, trust, openness", "Recklessness, delay, poor judgment"},
|
||||||
|
{1, "The Magician", "01_magician.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Willpower, skill, focused action", "Manipulation, scattered energy, unused talent"},
|
||||||
|
{2, "The High Priestess", "02_high_priestess.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Intuition, mystery, inner knowing", "Secrets, disconnection, blocked intuition"},
|
||||||
|
{3, "The Empress", "03_empress.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Nurture, abundance, creation", "Dependence, neglect, creative block"},
|
||||||
|
{4, "The Emperor", "04_emperor.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Structure, authority, protection", "Rigidity, control, instability"},
|
||||||
|
{5, "The Hierophant", "05_hierophant.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Tradition, learning, guidance", "Restriction, conformity, stale beliefs"},
|
||||||
|
{6, "The Lovers", "06_lovers.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Choice, union, values alignment", "Disharmony, avoidance, misalignment"},
|
||||||
|
{7, "The Chariot", "07_chariot.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Drive, control, victory", "Aggression, drift, lack of direction"},
|
||||||
|
{8, "Strength", "08_strength.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Courage, patience, inner power", "Self-doubt, force, low confidence"},
|
||||||
|
{9, "The Hermit", "09_hermit.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Reflection, solitude, wisdom", "Isolation, withdrawal, lost guidance"},
|
||||||
|
{10, "Wheel of Fortune", "10_wheel_of_fortune.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Cycles, fate, turning point", "Resistance, setbacks, repeated patterns"},
|
||||||
|
{11, "Justice", "11_justice.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Truth, balance, accountability", "Bias, dishonesty, unfair outcome"},
|
||||||
|
{12, "The Hanged Man", "12_hanged_man.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Pause, surrender, new perspective", "Stalling, resistance, needless sacrifice"},
|
||||||
|
{13, "Death", "13_death.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Ending, release, transformation", "Clinging, stagnation, fear of change"},
|
||||||
|
{14, "Temperance", "14_temperance.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Moderation, healing, integration", "Excess, imbalance, impatience"},
|
||||||
|
{15, "The Devil", "15_devil.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Attachment, temptation, shadow", "Release, awareness, breaking chains"},
|
||||||
|
{16, "The Tower", "16_tower.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Disruption, truth, sudden change", "Avoided collapse, fear, delayed truth"},
|
||||||
|
{17, "The Star", "17_star.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Hope, renewal, spiritual clarity", "Discouragement, depletion, lost faith"},
|
||||||
|
{18, "The Moon", "18_moon.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Dreams, illusion, subconscious", "Confusion lifting, denial, anxiety"},
|
||||||
|
{19, "The Sun", "19_sun.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Joy, success, vitality", "Doubt, muted joy, temporary delay"},
|
||||||
|
{20, "Judgement", "20_judgement.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Awakening, reckoning, renewal", "Self-judgment, avoidance, unfinished lesson"},
|
||||||
|
{21, "The World", "21_world.jpg", TAROT_ARCANA_MAJOR, TAROT_SUIT_NONE, "Completion, mastery, integration", "Incomplete cycle, delay, loose ends"},
|
||||||
|
|
||||||
|
{22, "Ace of Wands", "wands_ace.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Inspiration, spark, potential", "Delay, burnout, missed start"},
|
||||||
|
{23, "Two of Wands", "wands_02.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Planning, vision, decision", "Fear, small thinking, indecision"},
|
||||||
|
{24, "Three of Wands", "wands_03.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Expansion, foresight, progress", "Obstacles, limited reach, impatience"},
|
||||||
|
{25, "Four of Wands", "wands_04.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Celebration, home, stability", "Tension at home, private milestone, instability"},
|
||||||
|
{26, "Five of Wands", "wands_05.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Conflict, competition, friction", "Resolution, avoidance, inner conflict"},
|
||||||
|
{27, "Six of Wands", "wands_06.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Recognition, victory, confidence", "Ego, self-doubt, unseen effort"},
|
||||||
|
{28, "Seven of Wands", "wands_07.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Defense, courage, standing ground", "Overwhelm, giving up, weak boundaries"},
|
||||||
|
{29, "Eight of Wands", "wands_08.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Momentum, messages, swift change", "Delays, chaos, miscommunication"},
|
||||||
|
{30, "Nine of Wands", "wands_09.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Resilience, caution, endurance", "Exhaustion, paranoia, defensiveness"},
|
||||||
|
{31, "Ten of Wands", "wands_10.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Burden, responsibility, hard work", "Release, collapse, poor delegation"},
|
||||||
|
{32, "Page of Wands", "wands_page.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Curiosity, discovery, enthusiasm", "Restlessness, immaturity, false starts"},
|
||||||
|
{33, "Knight of Wands", "wands_knight.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Adventure, speed, bold action", "Impulsiveness, anger, scattered force"},
|
||||||
|
{34, "Queen of Wands", "wands_queen.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Confidence, warmth, charisma", "Jealousy, insecurity, demanding energy"},
|
||||||
|
{35, "King of Wands", "wands_king.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_WANDS, "Leadership, vision, command", "Domination, haste, poor follow-through"},
|
||||||
|
|
||||||
|
{36, "Ace of Cups", "cups_ace.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Love, openness, emotional renewal", "Blocked emotion, emptiness, guarded heart"},
|
||||||
|
{37, "Two of Cups", "cups_02.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Partnership, attraction, mutual care", "Separation, imbalance, strained bond"},
|
||||||
|
{38, "Three of Cups", "cups_03.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Friendship, support, celebration", "Gossip, overindulgence, isolation"},
|
||||||
|
{39, "Four of Cups", "cups_04.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Apathy, contemplation, reevaluation", "New interest, acceptance, emotional movement"},
|
||||||
|
{40, "Five of Cups", "cups_05.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Loss, regret, disappointment", "Recovery, forgiveness, perspective"},
|
||||||
|
{41, "Six of Cups", "cups_06.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Nostalgia, innocence, kindness", "Stuck in past, naivety, moving on"},
|
||||||
|
{42, "Seven of Cups", "cups_07.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Options, fantasy, imagination", "Clarity, choice, illusion exposed"},
|
||||||
|
{43, "Eight of Cups", "cups_08.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Departure, search, emotional growth", "Avoidance, fear of leaving, drifting"},
|
||||||
|
{44, "Nine of Cups", "cups_09.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Contentment, wish fulfilled, pleasure", "Dissatisfaction, excess, hollow success"},
|
||||||
|
{45, "Ten of Cups", "cups_10.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Harmony, family, lasting joy", "Disconnection, unrealistic ideal, tension"},
|
||||||
|
{46, "Page of Cups", "cups_page.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Sensitivity, message, creative feeling", "Emotional immaturity, insecurity, blocked art"},
|
||||||
|
{47, "Knight of Cups", "cups_knight.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Romance, invitation, idealism", "Moodiness, fantasy, empty promise"},
|
||||||
|
{48, "Queen of Cups", "cups_queen.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Compassion, intuition, emotional depth", "Overwhelm, codependence, poor boundaries"},
|
||||||
|
{49, "King of Cups", "cups_king.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_CUPS, "Calm, wisdom, emotional maturity", "Suppression, manipulation, volatility"},
|
||||||
|
|
||||||
|
{50, "Ace of Swords", "swords_ace.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Truth, clarity, breakthrough", "Confusion, harsh words, clouded judgment"},
|
||||||
|
{51, "Two of Swords", "swords_02.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Stalemate, decision, guarded peace", "Indecision, information overload, avoidance"},
|
||||||
|
{52, "Three of Swords", "swords_03.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Heartbreak, grief, painful truth", "Healing, release, lingering pain"},
|
||||||
|
{53, "Four of Swords", "swords_04.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Rest, recovery, stillness", "Burnout, unrest, forced pause"},
|
||||||
|
{54, "Five of Swords", "swords_05.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Conflict, defeat, hollow victory", "Reconciliation, regret, repeated conflict"},
|
||||||
|
{55, "Six of Swords", "swords_06.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Transition, passage, recovery", "Resistance, baggage, delayed move"},
|
||||||
|
{56, "Seven of Swords", "swords_07.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Strategy, secrecy, self-preservation", "Exposure, confession, poor tactics"},
|
||||||
|
{57, "Eight of Swords", "swords_08.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Restriction, fear, mental trap", "Freedom, new perspective, self-belief"},
|
||||||
|
{58, "Nine of Swords", "swords_09.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Anxiety, worry, sleeplessness", "Support, release, facing fear"},
|
||||||
|
{59, "Ten of Swords", "swords_10.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Ending, betrayal, rock bottom", "Recovery, survival, inevitable closure"},
|
||||||
|
{60, "Page of Swords", "swords_page.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Curiosity, vigilance, new ideas", "Gossip, haste, defensive thinking"},
|
||||||
|
{61, "Knight of Swords", "swords_knight.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Ambition, directness, fast thought", "Recklessness, aggression, tunnel vision"},
|
||||||
|
{62, "Queen of Swords", "swords_queen.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Honesty, discernment, independence", "Coldness, bitterness, severe judgment"},
|
||||||
|
{63, "King of Swords", "swords_king.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_SWORDS, "Logic, authority, clear standards", "Cruelty, misuse of power, rigidity"},
|
||||||
|
|
||||||
|
{64, "Ace of Pentacles", "pentacles_ace.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Opportunity, prosperity, seed", "Missed chance, scarcity, weak foundation"},
|
||||||
|
{65, "Two of Pentacles", "pentacles_02.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Balance, priorities, adaptation", "Overload, disorganization, imbalance"},
|
||||||
|
{66, "Three of Pentacles", "pentacles_03.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Teamwork, craft, learning", "Poor collaboration, low standards, misalignment"},
|
||||||
|
{67, "Four of Pentacles", "pentacles_04.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Security, control, saving", "Greed, fear, loosening grip"},
|
||||||
|
{68, "Five of Pentacles", "pentacles_05.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Hardship, exclusion, need", "Help, recovery, improved resources"},
|
||||||
|
{69, "Six of Pentacles", "pentacles_06.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Generosity, exchange, support", "Debt, strings attached, imbalance"},
|
||||||
|
{70, "Seven of Pentacles", "pentacles_07.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Patience, investment, assessment", "Impatience, wasted effort, poor results"},
|
||||||
|
{71, "Eight of Pentacles", "pentacles_08.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Skill, discipline, apprenticeship", "Perfectionism, shortcuts, dull routine"},
|
||||||
|
{72, "Nine of Pentacles", "pentacles_09.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Independence, luxury, self-sufficiency", "Dependence, overspending, insecurity"},
|
||||||
|
{73, "Ten of Pentacles", "pentacles_10.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Legacy, wealth, family structure", "Instability, family conflict, short-term thinking"},
|
||||||
|
{74, "Page of Pentacles", "pentacles_page.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Study, ambition, practical start", "Laziness, distraction, poor planning"},
|
||||||
|
{75, "Knight of Pentacles", "pentacles_knight.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Reliability, patience, steady work", "Stagnation, stubbornness, dullness"},
|
||||||
|
{76, "Queen of Pentacles", "pentacles_queen.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Care, practicality, resourcefulness", "Neglect, smothering, material anxiety"},
|
||||||
|
{77, "King of Pentacles", "pentacles_king.jpg", TAROT_ARCANA_MINOR, TAROT_SUIT_PENTACLES, "Stability, success, wise stewardship", "Greed, rigidity, status obsession"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tarot_card_t *tarot_deck_get_by_id(uint8_t id)
|
||||||
|
{
|
||||||
|
if (id >= TAROT_DECK_SIZE) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &tarot_deck[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *tarot_arcana_name(tarot_arcana_t arcana)
|
||||||
|
{
|
||||||
|
return arcana == TAROT_ARCANA_MAJOR ? "Major Arcana" : "Minor Arcana";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *tarot_suit_name(tarot_suit_t suit)
|
||||||
|
{
|
||||||
|
switch (suit) {
|
||||||
|
case TAROT_SUIT_WANDS:
|
||||||
|
return "Wands";
|
||||||
|
case TAROT_SUIT_CUPS:
|
||||||
|
return "Cups";
|
||||||
|
case TAROT_SUIT_SWORDS:
|
||||||
|
return "Swords";
|
||||||
|
case TAROT_SUIT_PENTACLES:
|
||||||
|
return "Pentacles";
|
||||||
|
case TAROT_SUIT_NONE:
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
44
main/tarot_deck.h
Normal file
44
main/tarot_deck.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAROT_DECK_SIZE 78
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TAROT_ARCANA_MAJOR = 0,
|
||||||
|
TAROT_ARCANA_MINOR,
|
||||||
|
} tarot_arcana_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TAROT_SUIT_NONE = 0,
|
||||||
|
TAROT_SUIT_WANDS,
|
||||||
|
TAROT_SUIT_CUPS,
|
||||||
|
TAROT_SUIT_SWORDS,
|
||||||
|
TAROT_SUIT_PENTACLES,
|
||||||
|
} tarot_suit_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t id;
|
||||||
|
const char *name;
|
||||||
|
const char *image_filename;
|
||||||
|
tarot_arcana_t arcana;
|
||||||
|
tarot_suit_t suit;
|
||||||
|
const char *upright_meaning;
|
||||||
|
const char *reversed_meaning;
|
||||||
|
} tarot_card_t;
|
||||||
|
|
||||||
|
extern const tarot_card_t tarot_deck[TAROT_DECK_SIZE];
|
||||||
|
|
||||||
|
const tarot_card_t *tarot_deck_get_by_id(uint8_t id);
|
||||||
|
const char *tarot_arcana_name(tarot_arcana_t arcana);
|
||||||
|
const char *tarot_suit_name(tarot_suit_t suit);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
71
main/tarot_logic.c
Normal file
71
main/tarot_logic.c
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#include "tarot_logic.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_random.h"
|
||||||
|
|
||||||
|
static const char *const s_spread_3[] = {
|
||||||
|
"Past",
|
||||||
|
"Present",
|
||||||
|
"Future",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const s_spread_8[] = {
|
||||||
|
"Current energy",
|
||||||
|
"Challenge",
|
||||||
|
"Hidden influence",
|
||||||
|
"Past root",
|
||||||
|
"Near future",
|
||||||
|
"Advice",
|
||||||
|
"External energy",
|
||||||
|
"Outcome",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *tarot_spread_name(tarot_spread_t spread)
|
||||||
|
{
|
||||||
|
switch (spread) {
|
||||||
|
case TAROT_SPREAD_3_CARD:
|
||||||
|
return "3-card";
|
||||||
|
case TAROT_SPREAD_8_CARD:
|
||||||
|
return "8-card";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *tarot_spread_position_meaning(tarot_spread_t spread, size_t index)
|
||||||
|
{
|
||||||
|
if (spread == TAROT_SPREAD_3_CARD && index < 3) {
|
||||||
|
return s_spread_3[index];
|
||||||
|
}
|
||||||
|
if (spread == TAROT_SPREAD_8_CARD && index < 8) {
|
||||||
|
return s_spread_8[index];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void tarot_logic_draw_reading(tarot_reading_t *reading, tarot_spread_t spread, bool reversed_enabled)
|
||||||
|
{
|
||||||
|
bool used[TAROT_DECK_SIZE] = { false };
|
||||||
|
size_t count = spread == TAROT_SPREAD_3_CARD ? 3 : 8;
|
||||||
|
|
||||||
|
if (reading == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(reading, 0, sizeof(*reading));
|
||||||
|
reading->spread = spread;
|
||||||
|
reading->count = count;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
uint8_t card_id = 0;
|
||||||
|
do {
|
||||||
|
card_id = esp_random() % TAROT_DECK_SIZE;
|
||||||
|
} while (used[card_id]);
|
||||||
|
|
||||||
|
used[card_id] = true;
|
||||||
|
reading->cards[i].card = &tarot_deck[card_id];
|
||||||
|
reading->cards[i].reversed = reversed_enabled ? ((esp_random() & 1U) == 1U) : false;
|
||||||
|
reading->cards[i].position_meaning = tarot_spread_position_meaning(spread, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
main/tarot_logic.h
Normal file
38
main/tarot_logic.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "tarot_deck.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAROT_MAX_SPREAD_CARDS 8
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TAROT_SPREAD_3_CARD = 3,
|
||||||
|
TAROT_SPREAD_8_CARD = 8,
|
||||||
|
} tarot_spread_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const tarot_card_t *card;
|
||||||
|
bool reversed;
|
||||||
|
const char *position_meaning;
|
||||||
|
} tarot_drawn_card_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tarot_spread_t spread;
|
||||||
|
size_t count;
|
||||||
|
tarot_drawn_card_t cards[TAROT_MAX_SPREAD_CARDS];
|
||||||
|
} tarot_reading_t;
|
||||||
|
|
||||||
|
const char *tarot_spread_name(tarot_spread_t spread);
|
||||||
|
const char *tarot_spread_position_meaning(tarot_spread_t spread, size_t index);
|
||||||
|
void tarot_logic_draw_reading(tarot_reading_t *reading, tarot_spread_t spread, bool reversed_enabled);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
215
main/ui_main.c
Normal file
215
main/ui_main.c
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#include "ui_main.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "card_assets.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "tarot_logic.h"
|
||||||
|
#include "ui_settings.h"
|
||||||
|
|
||||||
|
static const char *TAG = "ui_main";
|
||||||
|
|
||||||
|
static lv_style_t s_style_screen;
|
||||||
|
static lv_style_t s_style_title;
|
||||||
|
static lv_style_t s_style_button;
|
||||||
|
static lv_style_t s_style_card;
|
||||||
|
static lv_style_t s_style_muted;
|
||||||
|
static bool s_styles_ready;
|
||||||
|
static tarot_reading_t s_current_reading;
|
||||||
|
|
||||||
|
static void show_reading_cb(lv_event_t *event);
|
||||||
|
static void show_home_cb(lv_event_t *event);
|
||||||
|
static void show_settings_cb(lv_event_t *event);
|
||||||
|
|
||||||
|
static lv_obj_t *create_button(lv_obj_t *parent, const char *text, lv_event_cb_t cb)
|
||||||
|
{
|
||||||
|
lv_obj_t *button = lv_btn_create(parent);
|
||||||
|
lv_obj_add_style(button, &s_style_button, 0);
|
||||||
|
lv_obj_set_size(button, 210, 54);
|
||||||
|
lv_obj_add_event_cb(button, cb, LV_EVENT_CLICKED, NULL);
|
||||||
|
|
||||||
|
lv_obj_t *label = lv_label_create(button);
|
||||||
|
lv_label_set_text(label, text);
|
||||||
|
lv_obj_center(label);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_apply_current_theme(void)
|
||||||
|
{
|
||||||
|
tarot_settings_t settings = settings_get();
|
||||||
|
lv_color_t bg = lv_color_hex(0x111018);
|
||||||
|
lv_color_t fg = lv_color_hex(0xf5efe6);
|
||||||
|
lv_color_t card = lv_color_hex(0x201827);
|
||||||
|
lv_color_t accent = lv_color_hex(0x8d5cf6);
|
||||||
|
|
||||||
|
if (settings.theme == TAROT_THEME_LIGHT) {
|
||||||
|
bg = lv_color_hex(0xf7f0e6);
|
||||||
|
fg = lv_color_hex(0x221b18);
|
||||||
|
card = lv_color_hex(0xffffff);
|
||||||
|
accent = lv_color_hex(0x805b2f);
|
||||||
|
} else if (settings.theme == TAROT_THEME_MYSTIC) {
|
||||||
|
bg = lv_color_hex(0x080b19);
|
||||||
|
fg = lv_color_hex(0xf1e8ff);
|
||||||
|
card = lv_color_hex(0x151b36);
|
||||||
|
accent = lv_color_hex(0xc39bff);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s_styles_ready) {
|
||||||
|
lv_style_init(&s_style_screen);
|
||||||
|
lv_style_init(&s_style_title);
|
||||||
|
lv_style_init(&s_style_button);
|
||||||
|
lv_style_init(&s_style_card);
|
||||||
|
lv_style_init(&s_style_muted);
|
||||||
|
s_styles_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_style_reset(&s_style_screen);
|
||||||
|
lv_style_set_bg_color(&s_style_screen, bg);
|
||||||
|
lv_style_set_text_color(&s_style_screen, fg);
|
||||||
|
lv_style_set_pad_all(&s_style_screen, 18);
|
||||||
|
|
||||||
|
lv_style_reset(&s_style_title);
|
||||||
|
lv_style_set_text_color(&s_style_title, fg);
|
||||||
|
lv_style_set_text_font(&s_style_title, LV_FONT_DEFAULT);
|
||||||
|
|
||||||
|
lv_style_reset(&s_style_button);
|
||||||
|
lv_style_set_bg_color(&s_style_button, accent);
|
||||||
|
lv_style_set_text_color(&s_style_button, lv_color_white());
|
||||||
|
lv_style_set_radius(&s_style_button, 8);
|
||||||
|
|
||||||
|
lv_style_reset(&s_style_card);
|
||||||
|
lv_style_set_bg_color(&s_style_card, card);
|
||||||
|
lv_style_set_text_color(&s_style_card, fg);
|
||||||
|
lv_style_set_radius(&s_style_card, 8);
|
||||||
|
lv_style_set_pad_all(&s_style_card, 10);
|
||||||
|
lv_style_set_border_width(&s_style_card, 1);
|
||||||
|
lv_style_set_border_color(&s_style_card, accent);
|
||||||
|
|
||||||
|
lv_style_reset(&s_style_muted);
|
||||||
|
lv_style_set_text_color(&s_style_muted, lv_color_hex(0xaaa2b8));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_main_init(void)
|
||||||
|
{
|
||||||
|
ui_apply_current_theme();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_show_home(void)
|
||||||
|
{
|
||||||
|
ui_apply_current_theme();
|
||||||
|
|
||||||
|
lv_obj_t *screen = lv_obj_create(NULL);
|
||||||
|
lv_obj_add_style(screen, &s_style_screen, 0);
|
||||||
|
lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *title = lv_label_create(screen);
|
||||||
|
lv_obj_add_style(title, &s_style_title, 0);
|
||||||
|
lv_label_set_text(title, "ESP32-P4 Tarot");
|
||||||
|
lv_obj_set_style_text_font(title, LV_FONT_DEFAULT, 0);
|
||||||
|
|
||||||
|
lv_obj_t *subtitle = lv_label_create(screen);
|
||||||
|
lv_obj_add_style(subtitle, &s_style_muted, 0);
|
||||||
|
lv_label_set_text(subtitle, "On-device card art ready");
|
||||||
|
|
||||||
|
create_button(screen, "Draw Cards", show_reading_cb);
|
||||||
|
create_button(screen, "Settings", show_settings_cb);
|
||||||
|
|
||||||
|
lv_scr_load(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_card_tile(lv_obj_t *grid, const tarot_drawn_card_t *drawn, size_t index)
|
||||||
|
{
|
||||||
|
const tarot_card_t *card = drawn->card;
|
||||||
|
lv_obj_t *tile = lv_obj_create(grid);
|
||||||
|
lv_obj_add_style(tile, &s_style_card, 0);
|
||||||
|
lv_obj_set_size(tile, 230, 340);
|
||||||
|
lv_obj_set_flex_flow(tile, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *position = lv_label_create(tile);
|
||||||
|
lv_label_set_text_fmt(position, "%u. %s", (unsigned)(index + 1), drawn->position_meaning);
|
||||||
|
lv_label_set_long_mode(position, LV_LABEL_LONG_WRAP);
|
||||||
|
lv_obj_set_width(position, 205);
|
||||||
|
|
||||||
|
card_assets_create_card_art(tile, card, drawn->reversed);
|
||||||
|
|
||||||
|
lv_obj_t *name = lv_label_create(tile);
|
||||||
|
lv_label_set_text(name, card->name);
|
||||||
|
lv_label_set_long_mode(name, LV_LABEL_LONG_WRAP);
|
||||||
|
lv_obj_set_width(name, 205);
|
||||||
|
|
||||||
|
lv_obj_t *orientation = lv_label_create(tile);
|
||||||
|
lv_obj_add_style(orientation, &s_style_muted, 0);
|
||||||
|
lv_label_set_text(orientation, drawn->reversed ? "Reversed" : "Upright");
|
||||||
|
|
||||||
|
lv_obj_t *meaning = lv_label_create(tile);
|
||||||
|
lv_label_set_text(meaning, drawn->reversed ? card->reversed_meaning : card->upright_meaning);
|
||||||
|
lv_label_set_long_mode(meaning, LV_LABEL_LONG_WRAP);
|
||||||
|
lv_obj_set_width(meaning, 205);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_show_reading(void)
|
||||||
|
{
|
||||||
|
tarot_settings_t settings = settings_get();
|
||||||
|
tarot_logic_draw_reading(&s_current_reading, settings.selected_spread, settings.reversed_cards_enabled);
|
||||||
|
ESP_LOGI(TAG, "Drew %u unique tarot cards", (unsigned)s_current_reading.count);
|
||||||
|
|
||||||
|
lv_obj_t *screen = lv_obj_create(NULL);
|
||||||
|
lv_obj_add_style(screen, &s_style_screen, 0);
|
||||||
|
lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *header = lv_obj_create(screen);
|
||||||
|
lv_obj_set_width(header, LV_PCT(100));
|
||||||
|
lv_obj_set_height(header, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_bg_opa(header, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(header, 0, 0);
|
||||||
|
lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(header, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *title = lv_label_create(header);
|
||||||
|
lv_label_set_text_fmt(title, "%s Reading", tarot_spread_name(s_current_reading.spread));
|
||||||
|
|
||||||
|
lv_obj_t *actions = lv_obj_create(header);
|
||||||
|
lv_obj_set_style_bg_opa(actions, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(actions, 0, 0);
|
||||||
|
lv_obj_set_size(actions, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_flex_flow(actions, LV_FLEX_FLOW_ROW);
|
||||||
|
create_button(actions, "New Reading", show_reading_cb);
|
||||||
|
create_button(actions, "Back Home", show_home_cb);
|
||||||
|
|
||||||
|
lv_obj_t *grid = lv_obj_create(screen);
|
||||||
|
lv_obj_set_width(grid, LV_PCT(100));
|
||||||
|
lv_obj_set_flex_grow(grid, 1);
|
||||||
|
lv_obj_set_style_bg_opa(grid, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(grid, 0, 0);
|
||||||
|
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
|
||||||
|
lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < s_current_reading.count; i++) {
|
||||||
|
add_card_tile(grid, &s_current_reading.cards[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_scr_load(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_reading_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
ui_show_reading();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_home_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
ui_show_home();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_settings_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
ui_show_settings();
|
||||||
|
}
|
||||||
16
main/ui_main.h
Normal file
16
main/ui_main.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lvgl.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ui_main_init(void);
|
||||||
|
void ui_show_home(void);
|
||||||
|
void ui_show_reading(void);
|
||||||
|
void ui_apply_current_theme(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
151
main/ui_settings.c
Normal file
151
main/ui_settings.c
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#include "ui_settings.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "bsp/esp-bsp.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "ui_main.h"
|
||||||
|
|
||||||
|
static const char *TAG = "ui_settings";
|
||||||
|
|
||||||
|
static tarot_settings_t s_edit_settings;
|
||||||
|
|
||||||
|
static void back_cb(lv_event_t *event);
|
||||||
|
static void reversed_cb(lv_event_t *event);
|
||||||
|
static void save_readings_cb(lv_event_t *event);
|
||||||
|
static void brightness_cb(lv_event_t *event);
|
||||||
|
static void theme_cb(lv_event_t *event);
|
||||||
|
static void spread_cb(lv_event_t *event);
|
||||||
|
|
||||||
|
static void board_set_brightness(uint8_t brightness)
|
||||||
|
{
|
||||||
|
esp_err_t err = bsp_display_brightness_set(brightness);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set brightness: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static lv_obj_t *add_row(lv_obj_t *parent, const char *label_text)
|
||||||
|
{
|
||||||
|
lv_obj_t *row = lv_obj_create(parent);
|
||||||
|
lv_obj_set_width(row, LV_PCT(100));
|
||||||
|
lv_obj_set_height(row, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(row, 0, 0);
|
||||||
|
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *label = lv_label_create(row);
|
||||||
|
lv_label_set_text(label, label_text);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void save_current_settings(void)
|
||||||
|
{
|
||||||
|
esp_err_t err = settings_save(&s_edit_settings);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to save settings: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_show_settings(void)
|
||||||
|
{
|
||||||
|
s_edit_settings = settings_get();
|
||||||
|
ui_apply_current_theme();
|
||||||
|
|
||||||
|
lv_obj_t *screen = lv_obj_create(NULL);
|
||||||
|
lv_obj_set_style_pad_all(screen, 18, 0);
|
||||||
|
lv_obj_set_flex_flow(screen, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(screen, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
|
||||||
|
lv_obj_t *title = lv_label_create(screen);
|
||||||
|
lv_label_set_text(title, "Settings");
|
||||||
|
|
||||||
|
lv_obj_t *row = add_row(screen, "Reversed cards");
|
||||||
|
lv_obj_t *sw = lv_switch_create(row);
|
||||||
|
if (s_edit_settings.reversed_cards_enabled) {
|
||||||
|
lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||||
|
}
|
||||||
|
lv_obj_add_event_cb(sw, reversed_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
|
||||||
|
row = add_row(screen, "Save readings");
|
||||||
|
sw = lv_switch_create(row);
|
||||||
|
if (s_edit_settings.save_readings_enabled) {
|
||||||
|
lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||||
|
}
|
||||||
|
lv_obj_add_event_cb(sw, save_readings_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
|
||||||
|
row = add_row(screen, "Brightness");
|
||||||
|
lv_obj_t *slider = lv_slider_create(row);
|
||||||
|
lv_obj_set_width(slider, 190);
|
||||||
|
lv_slider_set_range(slider, 10, 100);
|
||||||
|
lv_slider_set_value(slider, s_edit_settings.brightness, LV_ANIM_OFF);
|
||||||
|
lv_obj_add_event_cb(slider, brightness_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
|
||||||
|
row = add_row(screen, "Theme");
|
||||||
|
lv_obj_t *theme = lv_dropdown_create(row);
|
||||||
|
lv_dropdown_set_options(theme, "Dark\nLight\nMystic");
|
||||||
|
lv_dropdown_set_selected(theme, s_edit_settings.theme);
|
||||||
|
lv_obj_set_width(theme, 170);
|
||||||
|
lv_obj_add_event_cb(theme, theme_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
|
||||||
|
row = add_row(screen, "Spread");
|
||||||
|
lv_obj_t *spread = lv_dropdown_create(row);
|
||||||
|
lv_dropdown_set_options(spread, "3-card\n8-card");
|
||||||
|
lv_dropdown_set_selected(spread, s_edit_settings.selected_spread == TAROT_SPREAD_3_CARD ? 0 : 1);
|
||||||
|
lv_obj_set_width(spread, 170);
|
||||||
|
lv_obj_add_event_cb(spread, spread_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
|
|
||||||
|
lv_obj_t *back = lv_btn_create(screen);
|
||||||
|
lv_obj_set_size(back, 190, 52);
|
||||||
|
lv_obj_add_event_cb(back, back_cb, LV_EVENT_CLICKED, NULL);
|
||||||
|
lv_obj_t *back_label = lv_label_create(back);
|
||||||
|
lv_label_set_text(back_label, "Back");
|
||||||
|
lv_obj_center(back_label);
|
||||||
|
|
||||||
|
lv_scr_load(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reversed_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
lv_obj_t *target = lv_event_get_target(event);
|
||||||
|
s_edit_settings.reversed_cards_enabled = lv_obj_has_state(target, LV_STATE_CHECKED);
|
||||||
|
save_current_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void save_readings_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
lv_obj_t *target = lv_event_get_target(event);
|
||||||
|
s_edit_settings.save_readings_enabled = lv_obj_has_state(target, LV_STATE_CHECKED);
|
||||||
|
save_current_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void brightness_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
lv_obj_t *target = lv_event_get_target(event);
|
||||||
|
s_edit_settings.brightness = (uint8_t)lv_slider_get_value(target);
|
||||||
|
board_set_brightness(s_edit_settings.brightness);
|
||||||
|
save_current_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void theme_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
lv_obj_t *target = lv_event_get_target(event);
|
||||||
|
s_edit_settings.theme = (tarot_theme_t)lv_dropdown_get_selected(target);
|
||||||
|
save_current_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spread_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
lv_obj_t *target = lv_event_get_target(event);
|
||||||
|
s_edit_settings.selected_spread = lv_dropdown_get_selected(target) == 0 ? TAROT_SPREAD_3_CARD : TAROT_SPREAD_8_CARD;
|
||||||
|
save_current_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void back_cb(lv_event_t *event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
ui_show_home();
|
||||||
|
}
|
||||||
13
main/ui_settings.h
Normal file
13
main/ui_settings.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lvgl.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ui_show_settings(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user