初始提交卡包 USB 工程

纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhengxin
2026-10-13 21:54:50 +08:00
co-authored by Cursor
commit 851e347cf5
260 changed files with 269349 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]
+13
View File
@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]
+21
View File
@@ -0,0 +1,21 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}
+94
View File
@@ -0,0 +1,94 @@
# 忽略构建目录
build/
cmake-build-*/
Debug/
Release/
# 忽略 VS Code 配置目录
.vscode/
# 忽略 组件
managed_components/
# 忽略编译生成的文件
*.obj
*.o
*.exe
*.dll
*.so
*.a
*.lib
*.pdb
*.ilk
# 忽略CMake生成的文件
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
Makefile
# 忽略Keil/IAR等IDE的文件
*.uvprojx
*.uvoptx
*.eww
*.ewp
*.ewd
# 忽略日志文件
*.log
logs/
# 忽略临时文件
*.tmp
*.temp
*~
.DS_Store
Thumbs.db
# 忽略其他可能的构建输出
_output/
bin/
obj/
# ESP-IDF specific files
*.bin
*.elf
*.map
/partition_table/
/bootloader/
/managed_components/
# nand_xfer 的锁文件含本机绝对路径,不能作为可复现依赖提交
dependencies.lock
sdkconfig
sdkconfig.old
flasher_args.json
phy_init_data.bin
ci/
idf_py_actions.yml
project_description.json
compile_commands.json
# Ignore build artifacts from ESP-IDF
build/bootloader/
build/partition_table/
build/phy_init_data.bin
build/config/sdkconfig.*
build/config/project_description.json
build/cmake/
build_support/
components/esptool_py/
components/partition_table/
components/bootloader/
# 本机缓存、虚拟环境和打包产物
.cache/
.venv*/
__pycache__/
*.pyc
build_*/
gui_build/
dist/
# nand_xfer 手工冒烟留下的临时文本,单元测试不依赖它们
nand_xfer/smoke*.txt
nand_xfer/test-*.txt
+11
View File
@@ -0,0 +1,11 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS
components
$ENV{IDF_PATH}/components
)
#set(PARTITIONS_TABLE_CSV partitions.csv)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mqtt5)
+78
View File
@@ -0,0 +1,78 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# ESP-MQTT sample application
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example connects to the broker URI selected using `idf.py menuconfig` (using mqtt tcp transport) and as a demonstration subscribes/unsubscribes and send a message on certain topic.
(Please note that the public broker is maintained by the community so may not be always available, for details please see this [disclaimer](https://iot.eclipse.org/getting-started/#sandboxes))
Note: If the URI equals `FROM_STDIN` then the broker address is read from stdin upon application startup (used for testing)
It uses ESP-MQTT library which implements mqtt client to connect to mqtt broker with MQTT version 5.
The more details about MQTT v5, please refer to [official website](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html)
## How to use example
### Hardware Required
This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet.
### Configure the project
* Open the project configuration menu (`idf.py menuconfig`)
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
* MQTT v5 protocol (`CONFIG_MQTT_PROTOCOL_5`) under "ESP-MQTT Configurations" menu is enabled by `sdkconfig.defaults`.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (5119) esp_netif_handlers: example_connect: sta ip: 192.168.3.143, mask: 255.255.255.0, gw: 192.168.3.1
I (5119) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.3.143
I (5619) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:c64f:33ff:fe24:6645, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5619) example_connect: Connected to example_connect: sta
I (5629) example_connect: - IPv4 address: 192.168.3.143
I (5629) example_connect: - IPv6 address: fe80:0000:0000:0000:c64f:33ff:fe24:6645, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5649) MQTT5_EXAMPLE: Other event id:7
W (6299) wifi:<ba-add>idx:0 (ifx:0, 34:29:12:43:c5:40), tid:7, ssn:0, winSize:64
I (7439) MQTT5_EXAMPLE: MQTT_EVENT_CONNECTED
I (7439) MQTT5_EXAMPLE: sent publish successful, msg_id=53118
I (7439) MQTT5_EXAMPLE: sent subscribe successful, msg_id=41391
I (7439) MQTT5_EXAMPLE: sent subscribe successful, msg_id=13695
I (7449) MQTT5_EXAMPLE: sent unsubscribe successful, msg_id=55594
I (7649) mqtt5_client: MQTT_MSG_TYPE_PUBACK return code is -1
I (7649) MQTT5_EXAMPLE: MQTT_EVENT_PUBLISHED, msg_id=53118
I (8039) mqtt5_client: MQTT_MSG_TYPE_SUBACK return code is 0
I (8049) MQTT5_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=41391
I (8049) MQTT5_EXAMPLE: sent publish successful, msg_id=0
I (8059) mqtt5_client: MQTT_MSG_TYPE_SUBACK return code is 2
I (8059) MQTT5_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=13695
I (8069) MQTT5_EXAMPLE: sent publish successful, msg_id=0
I (8079) MQTT5_EXAMPLE: MQTT_EVENT_DATA
I (8079) MQTT5_EXAMPLE: key is board, value is esp32
I (8079) MQTT5_EXAMPLE: key is u, value is user
I (8089) MQTT5_EXAMPLE: key is p, value is password
I (8089) MQTT5_EXAMPLE: payload_format_indicator is 1
I (8099) MQTT5_EXAMPLE: response_topic is /topic/test/response
I (8109) MQTT5_EXAMPLE: correlation_data is 123456
I (8109) MQTT5_EXAMPLE: content_type is
I (8119) MQTT5_EXAMPLE: TOPIC=/topic/qos1
I (8119) MQTT5_EXAMPLE: DATA=data_3
I (8129) mqtt5_client: MQTT_MSG_TYPE_UNSUBACK return code is 0
I (8129) MQTT5_EXAMPLE: MQTT_EVENT_UNSUBSCRIBED, msg_id=55594
I (8139) mqtt_client: Client asked to disconnect
I (9159) MQTT5_EXAMPLE: MQTT_EVENT_DISCONNECTED
```
+117
View File
@@ -0,0 +1,117 @@
# USB 传输操作步骤
本流程先把独立的 SPI NAND 传输固件烧进 ESP32-P4 的主应用分区,再用同一个 USB-Serial/JTAG 虚拟串口上传文件。文件保存在板上另一颗 GD5F1GM7 SPI NAND 中;最后烧回正式 APP 时,不会因覆盖主应用分区而删除 NAND 文件。
## 1. 准备电脑和连接板子
1. 用数据线连接板子的 Type-C 下载口(P4 的 USB_DM/USB_DP,USB-Serial/JTAG),在 Windows「设备管理器」的「端口 (COM 和 LPT)」中确认其 COM 编号。以下示例用 `COM3`,实际操作时统一替换成自己的编号。当前电脑没有检测到 COM 口,文档示例不代表已经在板上验证。
2. 关闭 `idf.py monitor`、串口调试助手等占用该 COM 口的程序。**烧录和上传先后使用同一个 COM 口,不需要第二个串口程序。**
3. 打开 Windows PowerShell,切换到传输工具目录:
```powershell
cd E:\PET\lab\kabao_usb\nand_xfer
```
4. 检查 Python 3 和串口库。这里必须用 `py -3`,不能用裸 `python`(本机的 `python` 可能是 2.7):
```powershell
py -3 --version
py -3 -m pip show pyserial
```
如果第二条显示未安装,再执行 `py -3 -m pip install pyserial`。上传脚本以 115200 波特率打开串口;USB-Serial/JTAG 可能忽略该波特率。
## 2. 烧录传输固件
**只烧这一个文件:**`E:\PET\lab\kabao_usb\nand_xfer\build_release\nand_xfer.bin`。它是独立传输 app,不是根工程的业务 APP,也不是 `bootloader.bin` 或 `partition-table.bin`。需要重新生成时,在当前目录运行:
```powershell
.\build_app.ps1
```
构建依赖 ESP-IDF 5.5.4 和对应的 ESP32-P4 工具链。
确认已找到上一节的 COM 口后,执行:
```powershell
.\flash_app.ps1 -Port COM3 -AppBin .\build_release\nand_xfer.bin
```
脚本核对本工程 `partitions.csv` 中 `ota_0` 为 `0x70000`、大小 `10M`,检查镜像,然后**只**执行一次 `write_flash 0x70000`。本流程以板子实际从该分区启动为前提,不修改启动选择。不要运行 `idf.py flash`、`erase-flash`,也不要使用 IDF 生成的 `flash_args`:那些命令可能包含 bootloader、分区表及 otadata。切勿整片擦除或改写 NVS、资源和其他数据分区。
烧录结束后板子重新启动。NAND 初始化成功时,这个 USB 虚拟串口会先输出 NAND 容量及挂载状态,随后输出单独一行 `NAND-XFER READY`;之后只有二进制传输帧,不要再开 monitor 来观察传输。没看到 READY 时不要上传,先检查端口、接线和启动状态。如果烧录后才打开端口而错过 READY,下一节的 `--info` 仍可直接握手查询。
## 3. 查询 NAND 状态
```powershell
py -3 nand_put.py COM3 --info
```
输出中的 `NAND` 是容量,`mounted=True` 表示已有 FAT 卷可写,`free` 是剩余空间。`mounted=False` 时普通上传会拒绝执行,**不会自动格式化**。如果这是确认没有需保留文件的空白 NAND,才单独运行:
```powershell
py -3 nand_put.py COM3 --format
```
脚本会要求手动输入 `FORMAT /extflash`;确认后设备才会格式化整个外置 NAND FAT 卷并重新挂载。**格式化会清空 NAND 中已有文件,不是上传的常规步骤。**完成后再次运行 `--info`,确认 `mounted=True`。如果 Windows 弹出“需要格式化磁盘”,选取消;不要用资源管理器格式化 NAND。Windows 不能直接按本卷的 NAND 逻辑页扇区布局浏览这些文件。
## 4. 上传单个文件
例如把电脑上的 `C:\files\demo.eaf` 放入设备卷的 `eaf` 目录,并在设备上命名为 `demo.eaf`:
```powershell
py -3 nand_put.py COM3 "C:\files\demo.eaf" "/extflash/eaf/demo.eaf"
```
命令结构是 `py -3 nand_put.py <COM口> "<电脑本地文件>" "<设备目标完整路径>"`。**最后一个参数就是设备上的目标路径**:这条命令创建或覆盖卷内 `eaf/demo.eaf`;`/extflash` 只是传输固件的挂载前缀,不是在 NAND 中额外创建的目录。中间目录不存在时由设备创建。文件名可用中文,带空格的路径用引号括住。
命令成功会输出 `OK`、设备目标路径、文件字节数及 `CRC32`;设备已关闭并同步文件,重新打开回读大小与 CRC 后才会报告成功。再独立查询一次:
```powershell
py -3 nand_put.py COM3 --verify "/extflash/eaf/demo.eaf"
py -3 nand_put.py COM3 --list "/extflash/eaf"
```
`--verify` 显示设备实际读到的大小和 CRC;`--list` 显示该目录里的文件名及大小。看到 `FILE demo.eaf` 就能确认文件位于卷内 `eaf` 目录。上传中断只可能影响正在写的那个文件;重新运行原上传命令会覆盖并校验该文件,之前成功完成的文件无需重新上传。
## 5. 上传整棵目录树
假设电脑目录 `C:\files\eaf` 中有 `dog.eaf`、`sub\cat.eaf` 和空目录 `empty`,要将**该目录的内容**放到设备的 `eaf` 目录:
```powershell
py -3 nand_put.py COM3 "C:\files\eaf" "/extflash/eaf" --recursive
```
对应关系如下;本地目录名本身不会在目标下再重复添加一层:
| 电脑本地位置 | 传输固件中的设备路径 | NAND 卷内相对路径 |
| --- | --- | --- |
| `C:\files\eaf\dog.eaf` | `/extflash/eaf/dog.eaf` | `eaf/dog.eaf` |
| `C:\files\eaf\sub\cat.eaf` | `/extflash/eaf/sub/cat.eaf` | `eaf/sub/cat.eaf` |
| `C:\files\eaf\empty` | `/extflash/eaf/empty` | `eaf/empty` |
脚本按相对路径上传文件并保留空目录;每个文件分别显示 `OK` 和 CRC。查询各层位置:
```powershell
py -3 nand_put.py COM3 --list "/extflash"
py -3 nand_put.py COM3 --list "/extflash/eaf"
py -3 nand_put.py COM3 --list "/extflash/eaf/sub"
```
列表中的 `DIR` 是子目录、`FILE` 是文件。也可以单独创建目录:`py -3 nand_put.py COM3 --mkdir "/extflash/eaf/newdir"`。再次运行目录上传会覆盖同名文件,但不会删除 NAND 上其他未包含在本地目录中的文件。
## 6. 复位后确认文件仍在
复位板子,等待其重新初始化;不要加 `--format`。再执行 `py -3 nand_put.py COM3 --info`,确认 `mounted=True`,然后运行上一节的 `--list` 和第四节的 `--verify`。已完成文件应继续存在且大小、CRC 一致。若不一致,停止恢复正式 APP,检查 NAND 接线、挂载和传输错误。
## 7. 烧回正式 APP
上传并验证完毕后,先确认正式 APP 的**实际 app bin 文件名与路径**,然后关闭所有占用 COM 口的程序,用同一脚本将正式 APP 烧回同一个偏移:
```powershell
.\flash_app.ps1 -Port COM3 -AppBin "E:\PET\fin_1\branch_main\IntegratedV1\build\<实际正式APP文件名>.bin"
```
请将尖括号部分替换为已构建的正式 APP 镜像名;不要直接照抄占位符。脚本仍只写内部 flash 的 `0x70000` 主应用分区,不碰另一颗 NAND 上的文件。正式 APP 把同一 NAND 卷挂载为 `/nandflash` 后,之前的 `/extflash/eaf/demo.eaf` 就对应它的 `/nandflash/eaf/demo.eaf`;卷内始终只有 `eaf/demo.eaf` 这一条相对路径。
**正式 APP 的 `fopen` 验收仍待板上验证。**当前 IntegratedV1 的 NAND GPIO 定义仍是旧引脚;本传输工具不会修改它,烧回未经引脚修正的正式 APP 后不能保证挂载成功。先完成正式 APP 的板级引脚适配与验证,再以它实际使用的 `/nandflash/...` 路径检查文件。
@@ -0,0 +1,7 @@
idf_component_register(SRCS "esp_lcd_qspi_amoled.c" INCLUDE_DIRS "include" PRIV_REQUIRES "driver" REQUIRES "esp_lcd")
include(package_manager)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})
@@ -0,0 +1,357 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "esp_log.h"
#include "esp_lcd_qspi_amoled.h"
#define LCD_OPCODE_WRITE_CMD (0x02ULL)
#define LCD_OPCODE_READ_CMD (0x03ULL)
#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
static const char *TAG = "qspi_amoled";
static esp_err_t panel_qspi_amoled_del(esp_lcd_panel_t *panel);
static esp_err_t panel_qspi_amoled_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_qspi_amoled_init(esp_lcd_panel_t *panel);
esp_err_t panel_qspi_amoled_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t panel_qspi_amoled_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_qspi_amoled_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_qspi_amoled_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t panel_qspi_amoled_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t panel_qspi_amoled_disp_on_off(esp_lcd_panel_t *panel, bool off);
typedef struct {
esp_lcd_panel_t base;
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
int x_gap;
int y_gap;
uint8_t fb_bits_per_pixel;
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register
const qspi_amoled_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int use_qspi_interface: 1;
unsigned int reset_level: 1;
} flags;
} qspi_amoled_panel_t;
esp_err_t esp_lcd_new_panel_qspi_amoled(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
{
ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_err_t ret = ESP_OK;
qspi_amoled_panel_t *qspi_amoled = NULL;
qspi_amoled = calloc(1, sizeof(qspi_amoled_panel_t));
ESP_GOTO_ON_FALSE(qspi_amoled, ESP_ERR_NO_MEM, err, TAG, "no mem for qspi_amoled panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
switch (panel_dev_config->rgb_ele_order) {
case LCD_RGB_ELEMENT_ORDER_RGB:
qspi_amoled->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
qspi_amoled->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
break;
}
uint8_t fb_bits_per_pixel = 0;
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
qspi_amoled->colmod_val = 0x55;
fb_bits_per_pixel = 16;
break;
case 18: // RGB666
qspi_amoled->colmod_val = 0x66;
// each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
fb_bits_per_pixel = 18;
break;
case 24: // RGB888
qspi_amoled->colmod_val = 0x77;
fb_bits_per_pixel = 24;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
qspi_amoled->io = io;
qspi_amoled->reset_gpio_num = panel_dev_config->reset_gpio_num;
qspi_amoled->fb_bits_per_pixel = fb_bits_per_pixel;
qspi_amoled_vendor_config_t *vendor_config = (qspi_amoled_vendor_config_t *)panel_dev_config->vendor_config;
if (vendor_config) {
qspi_amoled->init_cmds = vendor_config->init_cmds;
qspi_amoled->init_cmds_size = vendor_config->init_cmds_size;
qspi_amoled->flags.use_qspi_interface = vendor_config->flags.use_qspi_interface;
}
qspi_amoled->flags.reset_level = panel_dev_config->flags.reset_active_high;
qspi_amoled->base.del = panel_qspi_amoled_del;
qspi_amoled->base.reset = panel_qspi_amoled_reset;
qspi_amoled->base.init = panel_qspi_amoled_init;
qspi_amoled->base.draw_bitmap = panel_qspi_amoled_draw_bitmap;
qspi_amoled->base.invert_color = panel_qspi_amoled_invert_color;
qspi_amoled->base.set_gap = panel_qspi_amoled_set_gap;
qspi_amoled->base.mirror = panel_qspi_amoled_mirror;
qspi_amoled->base.swap_xy = panel_qspi_amoled_swap_xy;
qspi_amoled->base.disp_on_off = panel_qspi_amoled_disp_on_off;
*ret_panel = &(qspi_amoled->base);
ESP_LOGD(TAG, "new qspi_amoled panel @%p", qspi_amoled);
ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_QSPI_AMOLED_VER_MAJOR, ESP_LCD_QSPI_AMOLED_VER_MINOR,
ESP_LCD_QSPI_AMOLED_VER_PATCH);
return ESP_OK;
err:
if (qspi_amoled) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(qspi_amoled);
}
return ret;
}
static esp_err_t tx_param(qspi_amoled_panel_t *qspi_amoled, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size)
{
if (qspi_amoled->flags.use_qspi_interface) {
lcd_cmd &= 0xff;
lcd_cmd <<= 8;
lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
}
return esp_lcd_panel_io_tx_param(io, lcd_cmd, param, param_size);
}
static esp_err_t tx_color(qspi_amoled_panel_t *qspi_amoled, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size)
{
if (qspi_amoled->flags.use_qspi_interface) {
lcd_cmd &= 0xff;
lcd_cmd <<= 8;
lcd_cmd |= LCD_OPCODE_WRITE_COLOR << 24;
}
return esp_lcd_panel_io_tx_color(io, lcd_cmd, param, param_size);
}
static esp_err_t panel_qspi_amoled_del(esp_lcd_panel_t *panel)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
if (qspi_amoled->reset_gpio_num >= 0) {
gpio_reset_pin(qspi_amoled->reset_gpio_num);
}
ESP_LOGD(TAG, "del qspi_amoled panel @%p", qspi_amoled);
free(qspi_amoled);
return ESP_OK;
}
static esp_err_t panel_qspi_amoled_reset(esp_lcd_panel_t *panel)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
// Perform hardware reset
if (qspi_amoled->reset_gpio_num >= 0) {
gpio_set_level(qspi_amoled->reset_gpio_num, qspi_amoled->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(qspi_amoled->reset_gpio_num, !qspi_amoled->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(150));
} else { // Perform software reset
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(80));
}
return ESP_OK;
}
static const qspi_amoled_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
{0x44, (uint8_t []){0x01, 0xD1}, 2, 0},
{0x35, (uint8_t []){0x00}, 0, 0},
{0x53, (uint8_t []){0x20}, 1, 25},
};
static esp_err_t panel_qspi_amoled_init(esp_lcd_panel_t *panel)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
const qspi_amoled_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_cmd_overwritten = false;
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_MADCTL, (uint8_t[]) {
qspi_amoled->madctl_val,
}, 1), TAG, "send command failed");
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_COLMOD, (uint8_t[]) {
qspi_amoled->colmod_val,
}, 1), TAG, "send command failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (qspi_amoled->init_cmds) {
init_cmds = qspi_amoled->init_cmds;
init_cmds_size = qspi_amoled->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(qspi_amoled_lcd_init_cmd_t);
}
for (int i = 0; i < init_cmds_size; i++) {
// Check if the command has been used or conflicts with the internal
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
qspi_amoled->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
qspi_amoled->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
break;
default:
is_cmd_overwritten = false;
break;
}
if (is_cmd_overwritten) {
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
}
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG,
"send command failed");
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
}
ESP_LOGD(TAG, "send init commands success");
return ESP_OK;
}
esp_err_t panel_qspi_amoled_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
x_start += qspi_amoled->x_gap;
x_end += qspi_amoled->x_gap;
y_start += qspi_amoled->y_gap;
y_end += qspi_amoled->y_gap;
// define an area of frame memory where MCU can access
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_CASET, (uint8_t[]) {
(x_start >> 8) & 0xFF,
x_start & 0xFF,
((x_end - 1) >> 8) & 0xFF,
(x_end - 1) & 0xFF,
}, 4), TAG, "send command failed");
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_RASET, (uint8_t[]) {
(y_start >> 8) & 0xFF,
y_start & 0xFF,
((y_end - 1) >> 8) & 0xFF,
(y_end - 1) & 0xFF,
}, 4), TAG, "send command failed");
// transfer frame buffer
size_t len = (x_end - x_start) * (y_end - y_start) * qspi_amoled->fb_bits_per_pixel / 8;
ESP_RETURN_ON_ERROR(tx_color(qspi_amoled, io, LCD_CMD_RAMWR, color_data, len), TAG, "send color data failed");
return ESP_OK;
}
static esp_err_t panel_qspi_amoled_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
int command = 0;
if (invert_color_data) {
command = LCD_CMD_INVON;
} else {
command = LCD_CMD_INVOFF;
}
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
esp_err_t panel_qspi_amoled_set_brightness(esp_lcd_panel_t *panel, uint8_t brightness)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_WRDISBV, (uint8_t[]) {
brightness
}, 1), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_qspi_amoled_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
esp_err_t ret = ESP_OK;
if (mirror_x) {
qspi_amoled->madctl_val |= BIT(6);
} else {
qspi_amoled->madctl_val &= ~BIT(6);
}
if (mirror_y) {
ESP_LOGE(TAG, "mirror_y is not supported by this panel");
ret = ESP_ERR_NOT_SUPPORTED;
}
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, LCD_CMD_MADCTL, (uint8_t[]) {
qspi_amoled->madctl_val
}, 1), TAG, "send command failed");
return ret;
}
static esp_err_t panel_qspi_amoled_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
ESP_LOGE(TAG, "swap_xy is not supported by this panel");
return ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t panel_qspi_amoled_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
qspi_amoled->x_gap = x_gap;
qspi_amoled->y_gap = y_gap;
return ESP_OK;
}
static esp_err_t panel_qspi_amoled_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
qspi_amoled_panel_t *qspi_amoled = __containerof(panel, qspi_amoled_panel_t, base);
esp_lcd_panel_io_handle_t io = qspi_amoled->io;
int command = 0;
if (on_off) {
command = LCD_CMD_DISPON;
} else {
command = LCD_CMD_DISPOFF;
}
ESP_RETURN_ON_ERROR(tx_param(qspi_amoled, io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
@@ -0,0 +1,10 @@
dependencies:
cmake_utilities:
version: 0.*
idf:
version: '>5.0.4,!=5.1.1'
description: ESP LCD QSPI_AMOLED
issues: https://github.com/espressif/esp-iot-solution/issues
repository: https://github.com/espressif/esp-iot-solution.git
url: https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_qspi_amoled
version: 1.0.0
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LCD panel initialization commands.
*
*/
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} qspi_amoled_lcd_init_cmd_t;
/**
* @brief LCD panel vendor configuration.
*
* @note This structure can be used to select interface type and override default initialization commands.
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
*
*/
typedef struct {
const qspi_amoled_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array.
* The array should be declared as `static const` and positioned outside the function.
* Please refer to `vendor_specific_init_default` in source file
*/
uint16_t init_cmds_size; /*<! Number of commands in above array */
struct {
unsigned int use_qspi_interface: 1; /*<! Set to 1 if use QSPI interface, default is SPI interface */
} flags;
} qspi_amoled_vendor_config_t;
/**
* @brief Create LCD panel for model QSPI_AMOLED
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config General panel device configuration (Use `vendor_config` to select QSPI interface or override default initialization commands)
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_qspi_amoled(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
esp_err_t panel_qspi_amoled_set_brightness(esp_lcd_panel_t *panel, uint8_t brightness);
esp_err_t panel_qspi_amoled_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
/**
* @brief LCD panel bus configuration structure
*
*/
#define QSPI_AMOLED_PANEL_BUS_SPI_CONFIG(sclk, mosi, max_trans_sz) \
{ \
.sclk_io_num = sclk, \
.mosi_io_num = mosi, \
.miso_io_num = -1, \
.quadhd_io_num = -1, \
.quadwp_io_num = -1, \
.max_transfer_sz = max_trans_sz, \
}
#define QSPI_AMOLED_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
{ \
.sclk_io_num = sclk, \
.data0_io_num = d0, \
.data1_io_num = d1, \
.data2_io_num = d2, \
.data3_io_num = d3, \
.max_transfer_sz = max_trans_sz, \
}
/**
* @brief LCD panel IO configuration structure
*
*/
#define QSPI_AMOLED_PANEL_IO_SPI_CONFIG(cs, dc, cb, cb_ctx) \
{ \
.cs_gpio_num = cs, \
.dc_gpio_num = dc, \
.spi_mode = 0, \
.pclk_hz = 40 * 1000 * 1000, \
.trans_queue_depth = 10, \
.on_color_trans_done = cb, \
.user_ctx = cb_ctx, \
.lcd_cmd_bits = 8, \
.lcd_param_bits = 8, \
}
#define QSPI_AMOLED_PANEL_IO_QSPI_CONFIG(cs, cb, cb_ctx) \
{ \
.cs_gpio_num = cs, \
.dc_gpio_num = -1, \
.spi_mode = 0, \
.pclk_hz = 80 * 1000 * 1000, \
.trans_queue_depth = 10, \
.on_color_trans_done = cb, \
.user_ctx = cb_ctx, \
.lcd_cmd_bits = 32, \
.lcd_param_bits = 8, \
.flags = { \
.quad_mode = true, \
}, \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1 @@
idf_component_register(SRCS "esp_lcd_touch_chsc6417.c" INCLUDE_DIRS "include" REQUIRES "esp_lcd_touch")
@@ -0,0 +1,228 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#include "esp_lcd_touch_chsc6417.h"
#define POINT_NUM_MAX (1)
#define DATA_START_REG (0x00)
#define CHIP_ID_REG (0xA7)
static const char *TAG = "CHSC6417";
static esp_err_t read_data(esp_lcd_touch_handle_t tp);
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
static esp_err_t del(esp_lcd_touch_handle_t tp);
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t reset(esp_lcd_touch_handle_t tp);
static esp_err_t read_id(esp_lcd_touch_handle_t tp);
esp_err_t esp_lcd_touch_new_i2c_chsc6417(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "Invalid io");
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Invalid config");
ESP_RETURN_ON_FALSE(tp, ESP_ERR_INVALID_ARG, TAG, "Invalid touch handle");
/* Prepare main structure */
esp_err_t ret = ESP_OK;
esp_lcd_touch_handle_t chsc6417 = calloc(1, sizeof(esp_lcd_touch_t));
ESP_GOTO_ON_FALSE(chsc6417, ESP_ERR_NO_MEM, err, TAG, "Touch handle malloc failed");
/* Communication interface */
chsc6417->io = io;
/* Only supported callbacks are set */
chsc6417->read_data = read_data;
chsc6417->get_xy = get_xy;
chsc6417->del = del;
/* Mutex */
chsc6417->data.lock.owner = portMUX_FREE_VAL;
/* Save config */
memcpy(&chsc6417->config, config, sizeof(esp_lcd_touch_config_t));
/* Prepare pin for touch interrupt */
if (chsc6417->config.int_gpio_num != GPIO_NUM_NC) {
const gpio_config_t int_gpio_config = {
.mode = GPIO_MODE_INPUT,
.intr_type = (chsc6417->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE),
.pin_bit_mask = BIT64(chsc6417->config.int_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&int_gpio_config), err, TAG, "GPIO intr config failed");
/* Register interrupt callback */
if (chsc6417->config.interrupt_callback) {
esp_lcd_touch_register_interrupt_callback(chsc6417, chsc6417->config.interrupt_callback);
}
}
/* Prepare pin for touch controller reset */
if (chsc6417->config.rst_gpio_num != GPIO_NUM_NC) {
const gpio_config_t rst_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(chsc6417->config.rst_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&rst_gpio_config), err, TAG, "GPIO reset config failed");
}
/* Reset controller */
ESP_GOTO_ON_ERROR(reset(chsc6417), err, TAG, "Reset failed");
/* Read product id */
// ESP_GOTO_ON_ERROR(read_id(chsc6417), err, TAG, "Read version failed");
*tp = chsc6417;
ESP_LOGI(TAG, "LCD touch panel create success, version: %d.%d.%d", ESP_LCD_TOUCH_CHSC6417_VER_MAJOR, ESP_LCD_TOUCH_CHSC6417_VER_MINOR,
ESP_LCD_TOUCH_CHSC6417_VER_PATCH);
return ESP_OK;
err:
if (chsc6417) {
del(chsc6417);
}
ESP_LOGE(TAG, "Initialization failed!");
return ret;
}
static esp_err_t read_data(esp_lcd_touch_handle_t tp)
{
typedef struct {
uint8_t num;
uint8_t x_h : 4;
uint8_t : 4;
uint8_t x_l;
uint8_t y_h : 4;
uint8_t : 4;
uint8_t y_l;
} data_t;
data_t point;
uint8_t lvalue[3]={0};
// uint8_t gesture_id=0;
uint16_t x=0;
uint16_t y=0;
esp_err_t err = i2c_read_bytes(tp, 0xE0, (uint8_t *)lvalue, sizeof(lvalue));
// 断言
ESP_RETURN_ON_ERROR(err, TAG, "I2C read failed");
// 如果读取失败,将num置为0
if (err != ESP_OK) {
point.num = 0;
x = 0;
y = 0;
}else{
point.num = lvalue[0] & 0x03; // 获取触摸点数量
x = (uint16_t)((((lvalue[0] & 0x40) >> 6) << 8) | lvalue[1]);
y = (uint16_t)(((lvalue[0] & 0x80) >> 7) << 8) | lvalue[2];
}
// gesture_id = lvalue[0];
// ESP_LOGI(TAG, "Touch position: %d,%d,%d", point.num, x, y);
portENTER_CRITICAL(&tp->data.lock);
point.num = (point.num > POINT_NUM_MAX ? POINT_NUM_MAX : point.num);
tp->data.points = point.num;
/* Fill all coordinates */
for (int i = 0; i < point.num; i++) {
tp->data.coords[i].x = x;
tp->data.coords[i].y = y;
}
portEXIT_CRITICAL(&tp->data.lock);
return ESP_OK;
}
// void read_touch()
// {
// uint8_t data1[1]={0xE0};
// uint8_t lvalue[3];
// i2c_write(TOUCH_I2C_ADD,TOUCH_I2C_W,data1,1);
// delay(1);
// i2c_read(TOUCH_I2C_ADD,TOUCH_I2C_R,lvalue,3);
// point_num = lvalue[0] & 0x03;
// x = (uint16_t)((((lvalue[0] & 0x40) >> 6) << 8) | lvalue[1]);
// y = (uint16_t)(((lvalue[0] & 0x80) >> 7) << 8) | lvalue[2];
// }
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
portENTER_CRITICAL(&tp->data.lock);
/* Count of points */
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
for (size_t i = 0; i < *point_num; i++) {
x[i] = tp->data.coords[i].x;
y[i] = tp->data.coords[i].y;
if (strength) {
strength[i] = tp->data.coords[i].strength;
}
}
/* Invalidate */
tp->data.points = 0;
portEXIT_CRITICAL(&tp->data.lock);
return (*point_num > 0);
}
static esp_err_t del(esp_lcd_touch_handle_t tp)
{
/* Reset GPIO pin settings */
if (tp->config.int_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.int_gpio_num);
if (tp->config.interrupt_callback) {
gpio_isr_handler_remove(tp->config.int_gpio_num);
}
}
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.rst_gpio_num);
}
/* Release memory */
free(tp);
return ESP_OK;
}
static esp_err_t reset(esp_lcd_touch_handle_t tp)
{
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
}
return ESP_OK;
}
static esp_err_t read_id(esp_lcd_touch_handle_t tp)
{
uint8_t id;
ESP_RETURN_ON_ERROR(i2c_read_bytes(tp, CHIP_ID_REG, &id, 1), TAG, "I2C read failed");
ESP_LOGI(TAG, "IC id: %d", id);
return ESP_OK;
}
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "Invalid data");
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: CHSC6417
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_LCD_TOUCH_CHSC6417_VER_MAJOR (1)
#define ESP_LCD_TOUCH_CHSC6417_VER_MINOR (0)
#define ESP_LCD_TOUCH_CHSC6417_VER_PATCH (3)
/**
* @brief Create a new CHSC6417 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD panel IO handle, it should be created by `esp_lcd_new_panel_io_i2c()`
* @param config Touch panel configuration
* @param tp Touch panel handle
* @return
* - ESP_OK: on success
*/
esp_err_t esp_lcd_touch_new_i2c_chsc6417(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp);
/**
* @brief I2C address of the CHSC6417 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CHSC6417_ADDRESS (0x2E) // CHSC6417
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CHSC6417_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_CHSC6417_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 8, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1 @@
idf_component_register(SRCS "esp_lcd_touch_cst816.c" INCLUDE_DIRS "include" REQUIRES "esp_lcd_touch")
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#include "esp_lcd_touch_cst816.h"
#define POINT_NUM_MAX (1)
#define DATA_START_REG (0x00)
#define CHIP_ID_REG (0xA7)
static const char *TAG = "CST816";
static esp_err_t read_data(esp_lcd_touch_handle_t tp);
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
static esp_err_t del(esp_lcd_touch_handle_t tp);
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t reset(esp_lcd_touch_handle_t tp);
static esp_err_t read_id(esp_lcd_touch_handle_t tp);
esp_err_t esp_lcd_touch_new_i2c_cst816(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "Invalid io");
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Invalid config");
ESP_RETURN_ON_FALSE(tp, ESP_ERR_INVALID_ARG, TAG, "Invalid touch handle");
/* Prepare main structure */
esp_err_t ret = ESP_OK;
esp_lcd_touch_handle_t cst816 = calloc(1, sizeof(esp_lcd_touch_t));
ESP_GOTO_ON_FALSE(cst816, ESP_ERR_NO_MEM, err, TAG, "Touch handle malloc failed");
/* Communication interface */
cst816->io = io;
/* Only supported callbacks are set */
cst816->read_data = read_data;
cst816->get_xy = get_xy;
cst816->del = del;
/* Mutex */
cst816->data.lock.owner = portMUX_FREE_VAL;
/* Save config */
memcpy(&cst816->config, config, sizeof(esp_lcd_touch_config_t));
/* Prepare pin for touch interrupt */
if (cst816->config.int_gpio_num != GPIO_NUM_NC) {
const gpio_config_t int_gpio_config = {
.mode = GPIO_MODE_INPUT,
.intr_type = (cst816->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE),
.pin_bit_mask = BIT64(cst816->config.int_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&int_gpio_config), err, TAG, "GPIO intr config failed");
/* Register interrupt callback */
if (cst816->config.interrupt_callback) {
esp_lcd_touch_register_interrupt_callback(cst816, cst816->config.interrupt_callback);
}
}
/* Prepare pin for touch controller reset */
if (cst816->config.rst_gpio_num != GPIO_NUM_NC) {
const gpio_config_t rst_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(cst816->config.rst_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&rst_gpio_config), err, TAG, "GPIO reset config failed");
}
/* Reset controller */
ESP_GOTO_ON_ERROR(reset(cst816), err, TAG, "Reset failed");
/* Read product id */
ESP_GOTO_ON_ERROR(read_id(cst816), err, TAG, "Read version failed");
*tp = cst816;
ESP_LOGI(TAG, "LCD touch panel create success, version: %d.%d.%d", ESP_LCD_TOUCH_CST816_VER_MAJOR, ESP_LCD_TOUCH_CST816_VER_MINOR,
ESP_LCD_TOUCH_CST816_VER_PATCH);
return ESP_OK;
err:
if (cst816) {
del(cst816);
}
ESP_LOGE(TAG, "Initialization failed!");
return ret;
}
static esp_err_t read_data(esp_lcd_touch_handle_t tp)
{
typedef struct {
uint8_t num;
uint8_t x_h : 4;
uint8_t : 4;
uint8_t x_l;
uint8_t y_h : 4;
uint8_t : 4;
uint8_t y_l;
} data_t;
data_t point;
uint8_t lvalue[15]={0};
uint8_t gesture_id=0;
uint16_t x=0;
uint16_t y=0;
ESP_RETURN_ON_ERROR(i2c_read_bytes(tp, DATA_START_REG, (uint8_t *)lvalue, sizeof(lvalue)), TAG, "I2C read failed");
gesture_id = lvalue[1];
point.num =lvalue[2];
x = (((uint16_t)(lvalue[3]&0x0f))<<8) | lvalue[4];
y = (((uint16_t)(lvalue[5]&0x0f))<<8) | lvalue[6];
portENTER_CRITICAL(&tp->data.lock);
point.num = (point.num > POINT_NUM_MAX ? POINT_NUM_MAX : point.num);
tp->data.points = point.num;
/* Fill all coordinates */
for (int i = 0; i < point.num; i++) {
tp->data.coords[i].x = x;
tp->data.coords[i].y = y;
}
portEXIT_CRITICAL(&tp->data.lock);
return ESP_OK;
}
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
portENTER_CRITICAL(&tp->data.lock);
/* Count of points */
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
for (size_t i = 0; i < *point_num; i++) {
x[i] = tp->data.coords[i].x;
y[i] = tp->data.coords[i].y;
if (strength) {
strength[i] = tp->data.coords[i].strength;
}
}
/* Invalidate */
tp->data.points = 0;
portEXIT_CRITICAL(&tp->data.lock);
return (*point_num > 0);
}
static esp_err_t del(esp_lcd_touch_handle_t tp)
{
/* Reset GPIO pin settings */
if (tp->config.int_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.int_gpio_num);
if (tp->config.interrupt_callback) {
gpio_isr_handler_remove(tp->config.int_gpio_num);
}
}
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.rst_gpio_num);
}
/* Release memory */
free(tp);
return ESP_OK;
}
static esp_err_t reset(esp_lcd_touch_handle_t tp)
{
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
}
return ESP_OK;
}
static esp_err_t read_id(esp_lcd_touch_handle_t tp)
{
uint8_t id;
ESP_RETURN_ON_ERROR(i2c_read_bytes(tp, CHIP_ID_REG, &id, 1), TAG, "I2C read failed");
ESP_LOGI(TAG, "IC id: %d", id);
return ESP_OK;
}
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "Invalid data");
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: CST816
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_LCD_TOUCH_CST816_VER_MAJOR (1)
#define ESP_LCD_TOUCH_CST816_VER_MINOR (0)
#define ESP_LCD_TOUCH_CST816_VER_PATCH (3)
/**
* @brief Create a new CST816 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD panel IO handle, it should be created by `esp_lcd_new_panel_io_i2c()`
* @param config Touch panel configuration
* @param tp Touch panel handle
* @return
* - ESP_OK: on success
*/
esp_err_t esp_lcd_touch_new_i2c_cst816(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp);
/**
* @brief I2C address of the CST816 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CST816_ADDRESS (0x15) // CST816
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CST816_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 8, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1 @@
idf_component_register(SRCS "esp_lcd_touch_cst820.c" INCLUDE_DIRS "include" REQUIRES "esp_lcd_touch")
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#include "esp_lcd_touch_cst820.h"
#define POINT_NUM_MAX (1)
#define DATA_START_REG (0x00)
#define CHIP_ID_REG (0xA7)
static const char *TAG = "CST820";
static esp_err_t read_data(esp_lcd_touch_handle_t tp);
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
static esp_err_t del(esp_lcd_touch_handle_t tp);
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t reset(esp_lcd_touch_handle_t tp);
static esp_err_t read_id(esp_lcd_touch_handle_t tp);
esp_err_t esp_lcd_touch_new_i2c_cst820(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "Invalid io");
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Invalid config");
ESP_RETURN_ON_FALSE(tp, ESP_ERR_INVALID_ARG, TAG, "Invalid touch handle");
/* Prepare main structure */
esp_err_t ret = ESP_OK;
esp_lcd_touch_handle_t cst820 = calloc(1, sizeof(esp_lcd_touch_t));
ESP_GOTO_ON_FALSE(cst820, ESP_ERR_NO_MEM, err, TAG, "Touch handle malloc failed");
/* Communication interface */
cst820->io = io;
/* Only supported callbacks are set */
cst820->read_data = read_data;
cst820->get_xy = get_xy;
cst820->del = del;
/* Mutex */
cst820->data.lock.owner = portMUX_FREE_VAL;
/* Save config */
memcpy(&cst820->config, config, sizeof(esp_lcd_touch_config_t));
/* Prepare pin for touch interrupt */
if (cst820->config.int_gpio_num != GPIO_NUM_NC) {
const gpio_config_t int_gpio_config = {
.mode = GPIO_MODE_INPUT,
.intr_type = (cst820->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE),
.pin_bit_mask = BIT64(cst820->config.int_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&int_gpio_config), err, TAG, "GPIO intr config failed");
/* Register interrupt callback */
if (cst820->config.interrupt_callback) {
esp_lcd_touch_register_interrupt_callback(cst820, cst820->config.interrupt_callback);
}
}
/* Prepare pin for touch controller reset */
if (cst820->config.rst_gpio_num != GPIO_NUM_NC) {
const gpio_config_t rst_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(cst820->config.rst_gpio_num)
};
ESP_GOTO_ON_ERROR(gpio_config(&rst_gpio_config), err, TAG, "GPIO reset config failed");
}
/* Reset controller */
ESP_GOTO_ON_ERROR(reset(cst820), err, TAG, "Reset failed");
/* Read product id */
ESP_GOTO_ON_ERROR(read_id(cst820), err, TAG, "Read version failed");
*tp = cst820;
ESP_LOGI(TAG, "LCD touch panel create success, version: %d.%d.%d", ESP_LCD_TOUCH_CST820_VER_MAJOR, ESP_LCD_TOUCH_CST820_VER_MINOR,
ESP_LCD_TOUCH_CST820_VER_PATCH);
return ESP_OK;
err:
if (cst820) {
del(cst820);
}
ESP_LOGE(TAG, "Initialization failed!");
return ret;
}
static esp_err_t read_data(esp_lcd_touch_handle_t tp)
{
typedef struct {
uint8_t num;
uint8_t x_h : 4;
uint8_t : 4;
uint8_t x_l;
uint8_t y_h : 4;
uint8_t : 4;
uint8_t y_l;
} data_t;
data_t point;
uint8_t lvalue[15]={0};
uint8_t gesture_id=0;
uint16_t x=0;
uint16_t y=0;
ESP_RETURN_ON_ERROR(i2c_read_bytes(tp, DATA_START_REG, (uint8_t *)lvalue, sizeof(lvalue)), TAG, "I2C read failed");
gesture_id = lvalue[1];
point.num =lvalue[2];
x = (((uint16_t)(lvalue[3]&0x0f))<<8) | lvalue[4];
y = (((uint16_t)(lvalue[5]&0x0f))<<8) | lvalue[6];
portENTER_CRITICAL(&tp->data.lock);
point.num = (point.num > POINT_NUM_MAX ? POINT_NUM_MAX : point.num);
tp->data.points = point.num;
/* Fill all coordinates */
for (int i = 0; i < point.num; i++) {
tp->data.coords[i].x = x;
tp->data.coords[i].y = y;
}
portEXIT_CRITICAL(&tp->data.lock);
return ESP_OK;
}
static bool get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
portENTER_CRITICAL(&tp->data.lock);
/* Count of points */
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
for (size_t i = 0; i < *point_num; i++) {
x[i] = tp->data.coords[i].x;
y[i] = tp->data.coords[i].y;
if (strength) {
strength[i] = tp->data.coords[i].strength;
}
}
/* Invalidate */
tp->data.points = 0;
portEXIT_CRITICAL(&tp->data.lock);
return (*point_num > 0);
}
static esp_err_t del(esp_lcd_touch_handle_t tp)
{
/* Reset GPIO pin settings */
if (tp->config.int_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.int_gpio_num);
if (tp->config.interrupt_callback) {
gpio_isr_handler_remove(tp->config.int_gpio_num);
}
}
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.rst_gpio_num);
}
/* Release memory */
free(tp);
return ESP_OK;
}
static esp_err_t reset(esp_lcd_touch_handle_t tp)
{
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level failed");
vTaskDelay(pdMS_TO_TICKS(200));
}
return ESP_OK;
}
static esp_err_t read_id(esp_lcd_touch_handle_t tp)
{
uint8_t id;
ESP_RETURN_ON_ERROR(i2c_read_bytes(tp, CHIP_ID_REG, &id, 1), TAG, "I2C read failed");
ESP_LOGI(TAG, "IC id: %d", id);
return ESP_OK;
}
static esp_err_t i2c_read_bytes(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "Invalid data");
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: CST820
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_LCD_TOUCH_CST820_VER_MAJOR (1)
#define ESP_LCD_TOUCH_CST820_VER_MINOR (0)
#define ESP_LCD_TOUCH_CST820_VER_PATCH (3)
/**
* @brief Create a new CST820 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD panel IO handle, it should be created by `esp_lcd_new_panel_io_i2c()`
* @param config Touch panel configuration
* @param tp Touch panel handle
* @return
* - ESP_OK: on success
*/
esp_err_t esp_lcd_touch_new_i2c_cst820(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *tp);
/**
* @brief I2C address of the CST820 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CST820_ADDRESS (0x15) // CST820
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_CST820_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_CST820_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 8, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
+4
View File
@@ -0,0 +1,4 @@
idf_component_register(
SRCS "i2c_bsp.c"
REQUIRES driver
INCLUDE_DIRS "./")
+56
View File
@@ -0,0 +1,56 @@
#include <stdio.h>
#include "i2c_bsp.h"
#define TEST_I2C_PORT I2C_NUM_0
//#define EXAMPLE_USE_TOUCH //触控屏的实现
#ifndef EXAMPLE_USE_TOUCH
#define I2C_MASTER_SCL_IO 39
#define I2C_MASTER_SDA_IO 40
void I2C_master_Init(void)
{
i2c_config_t conf =
{
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO, // 配置 SDA 的 GPIO
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO, // 配置 SCL 的 GPIO
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 300 * 1000, // 为项目选择频率
.clk_flags = 0, // 可选项,可以使用 I2C_SCLK_SRC_FLAG_* 标志来选择 I2C 源时钟
};
ESP_ERROR_CHECK(i2c_param_config(TEST_I2C_PORT, &conf));
ESP_ERROR_CHECK(i2c_driver_install(TEST_I2C_PORT, conf.mode,0,0,0));
}
#endif
uint8_t I2C_writr_buff(uint8_t addr,uint8_t reg,uint8_t *buf,uint8_t len)
{
uint8_t ret;
uint8_t *pbuf = (uint8_t*)malloc(len+1);
pbuf[0] = reg;
for(uint8_t i = 0; i<len; i++)
{
pbuf[i+1] = buf[i];
}
ret = i2c_master_write_to_device(TEST_I2C_PORT,addr,pbuf,len+1,1000);
free(pbuf);
pbuf = NULL;
return ret;
}
uint8_t I2C_read_buff(uint8_t addr,uint8_t reg,uint8_t *buf,uint8_t len)
{
uint8_t ret;
ret = i2c_master_write_read_device(TEST_I2C_PORT,addr,&reg,1,buf,len,1000);
return ret;
}
uint8_t I2C_master_write_read_device(uint8_t addr,uint8_t *writeBuf,uint8_t writeLen,uint8_t *readBuf,uint8_t readLen)
{
uint8_t ret;
ret = i2c_master_write_read_device(TEST_I2C_PORT,addr,writeBuf,writeLen,readBuf,readLen,1000);
return ret;
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef I2C_BSP_H
#define I2C_BSP_H
#include "driver/i2c.h"
void I2C_master_Init(void);
uint8_t I2C_writr_buff(uint8_t addr,uint8_t reg,uint8_t *buf,uint8_t len);
uint8_t I2C_read_buff(uint8_t addr,uint8_t reg,uint8_t *buf,uint8_t len);
uint8_t I2C_master_write_read_device(uint8_t addr,uint8_t *writeBuf,uint8_t writeLen,uint8_t *readBuf,uint8_t readLen);
#endif
+142
View File
@@ -0,0 +1,142 @@
# 外部 Flash (NAND) 访问时序与并发分析报告
## 1. 概述
本报告分析 `/extflash` 挂载的 NAND Flash 在动画切换、UI 操作、下载解压等场景下的访问时序与潜在并发问题。
---
## 2. extflash 访问点汇总
### 2.1 读访问(Lottie JSON、stat、opendir)
| 调用位置 | 任务/上下文 | 操作 | 触发时机 |
|----------|-------------|------|----------|
| `switch_lottie_animation_cross_path_internal` | change_task (core 0) | `stat()` + `lv_lottie_set_src_file()` (fopen/fread) | BLE 指令、动画完成回调、定时器、下载成功 |
| `main_task` | main_task (core 1) | `stat(path_buf)` | 处理 DOWNLOAD_SUCCESS_BIT 时 |
| `extract_task` | extract_task (core 1) | `list_files_in_directory()` (opendir/readdir), `stat()` | 解压完成后 |
| `emoji_storage_sync_downloaded_packs_from_fs` | 调用者上下文 | opendir/readdir/stat | emoji_storage 初始化 |
| `zip_extractor` | extract_task 内 | `stat(zip_file_path)` | 解压前检查 ZIP 存在 |
| `change_task` 路径解析 | change_task | 多处 `stat()` | 解析表情包路径时 |
### 2.2 写访问
| 调用位置 | 任务/上下文 | 操作 | 触发时机 |
|----------|-------------|------|----------|
| `download_file` | MQTT 回调上下文 | fopen/fwrite (ZIP 下载) | MQTT 收到下载 URL 后 |
| `extract_zip_file` | extract_task (core 1) | fopen/fwrite (解压 JSON) | 下载完成后 |
| `check_flash_integrity` | flash_main 初始化 | fopen/fwrite | 启动时 |
| OTA `extract_running_app_to_storage` | OTA 流程 | 写入 `/extflash/flash.bin` | OTA 升级时 |
---
## 3. 并发场景分析
### 3.1 典型并发组合
| 场景 | 任务 A | 任务 B | 是否可能并发 |
|------|--------|--------|--------------|
| 1 | change_task 读 Lottie | extract_task 写解压文件 | 是 |
| 2 | change_task 读 Lottie | download_file 写 ZIP | 是 |
| 3 | main_task stat | extract_task 写 | 是 |
| 4 | 动画切换 | 用户手势/UI 操作 | 是(逻辑上并发) |
### 3.2 底层串行化
- **VFS/FAT 层**:ESP-IDF 的 fatfs 挂载使用 `ff_mutex`,同一时刻只允许一个文件操作。
- **NAND 硬件**:单 SPI 总线,单设备,物理上只能串行传输。
因此,**不会出现真正的硬件级并发读写**,但会出现**多个任务排队等待**同一 NAND 访问。
---
## 4. 关键问题
### 4.1 持锁期间进行 NAND 读(UI 卡顿)
`lvgl_init.c` 中:
```c
// 约 1185-1188 行
_lock_acquire(&lvgl_api_lock);
lv_lottie_set_src_file(current_lottie, file_path); // 内部会 fopen/fread 从 NAND 读
// ...
_lock_release(&lvgl_api_lock);
```
- `lv_lottie_set_src_file` 会从 NAND 读取整个 Lottie JSON,耗时较长。
- 整个读取过程在持有 `lvgl_api_lock` 期间执行。
- LVGL 任务需要该锁才能刷新界面,因此**动画切换期间 UI 会冻结**。
- 若用户在切换过程中滑动手势,UI 回调会卡在等待锁释放。
### 4.2 写操作阻塞读
- `extract_task` 解压大量 JSON 时,会长时间占用 FAT 锁。
- 此时 `change_task` 若执行 `stat()` 或 `lv_lottie_set_src_file()`,会阻塞在 FAT 层。
- 导致动画切换延迟或卡顿。
### 4.3 读操作阻塞写
- `change_task` 正在读取 Lottie 时,若 `extract_task` 或 `download_file` 开始写,会阻塞在 FAT 层。
- 下载/解压时间被拉长。
### 4.4 UI 手势与 extflash 的关系
- `ui_event_gesture_cb` 不直接访问 extflash,只做 `ui_change_page`(lv_screen_load_anim)。
- 但若此时 `change_task` 正在执行 `switch_lottie_animation_cross_path_internal` 并持有 `lvgl_api_lock`,LVGL 任务会阻塞,手势处理会延迟。
---
## 5. 任务与核心分布
| 任务 | 核心 | 优先级 | 主要 extflash 操作 |
|------|------|--------|--------------------|
| change_task | 0 | CHANGE_TASK_PRIORITY | 读 Lottie、stat |
| 主 LVGL 任务 | 0 | EXAMPLE_LVGL_TASK_PRIORITY | 无直接访问 |
| main_task | 1 | - | stat |
| extract_task | 1 | configMAX_PRIORITIES - 1 | 写解压、list_files、stat |
| download_file | 调用者上下文 (MQTT) | - | 写 ZIP |
---
## 6. 建议措施
### 6.1 短期(缓解 UI 卡顿)
1. **分离锁与文件读取**:
- 先 `stat()` 检查文件是否存在(不持 lvgl 锁)。
- 在持锁前,将文件内容读入内存缓冲区(或使用 LVGL 支持的内存源)。
- 持锁期间只做 `lv_lottie_set_src_buffer` 或等效 API,不进行 NAND 读。
2. **缩短持锁时间**:
- 若无法改为内存源,至少将 `lv_lottie_set_src_file` 移出锁外,仅把 LVGL 对象创建/修改等必须持锁的部分放在锁内。
### 6.2 中期(统一访问顺序)
1. **增加 extflash 访问互斥锁**:
- 所有对 `/extflash` 的 fopen/fread/fwrite/stat/opendir 等操作统一通过一个 `extflash_mutex` 保护。
- 避免长时间写阻塞短时读,可考虑读写锁(如支持)或优先级策略。
2. **解压时暂停动画切换**:
- 解压开始前设置标志,`change_task` 收到切换请求时若发现解压中则排队或延后。
- 解压完成后再处理排队请求。
### 6.3 长期(架构优化)
1. **预加载/缓存**:
- 常用 Lottie 预先读入内存或临时文件,切换时减少 NAND 访问。
2. **异步加载**:
- 先显示占位或上一帧,后台异步读 Lottie,完成后切换。
3. **NAND 性能调优**:
- 检查 SPI 时钟、DMA、队列等配置,在稳定前提下提升吞吐。
---
## 7. 结论
- **并发访问**:不会出现硬件级并发,但多任务会排队访问 NAND,存在相互阻塞。
- **动画切换**:在持 `lvgl_api_lock` 期间进行 NAND 读,会导致 UI 卡顿。
- **UI 操作**:手势本身不访问 extflash,但会因等待 `lvgl_api_lock` 而受动画切换影响。
- **建议**:优先将持锁与 NAND 读分离,并考虑引入 extflash 访问互斥与解压/切换协调逻辑。
+21
View File
@@ -0,0 +1,21 @@
# SPI 总线冲突检查报告
## 结论:无 SPI 总线竞争
外部 Flash (NAND)、LCD、触摸屏使用**不同的总线**,不存在 SPI 总线冲突。
## 总线分配
| 外设 | 总线类型 | 主机/端口 | 引脚 |
|------|----------|-----------|------|
| **NAND Flash** (GD5F1GM7) | SPI3 (VSPI) | SPI3_HOST | MOSI=11, MISO=13, CLK=12, CS=10 |
| **LCD** (QSPI AMOLED) | SPI2 (HSPI) | SPI2_HOST / LCD_HOST | CS=8, PCLK=3, DATA0=9, DATA1=46, DATA2=16, DATA3=15 |
| **触摸屏** (CST820) | I2C | I2C_NUM_0 | SCL=7, SDA=6 |
## 说明
- **NAND** 使用 SPI3_HOST (VSPI),独立 SPI 总线
- **LCD** 使用 SPI2_HOST (HSPI),独立 SPI 总线
- **触摸** 使用 I2C,与 SPI 完全独立
因此 NAND 读写与 LCD 刷新、触摸读取不会产生总线竞争。
+54
View File
@@ -0,0 +1,54 @@
#include "crc.h"
#include "config.h"
// 蓝牙标准CRC16-CCITT多项式
#define CRC16_CCITT_POLY 0x1021
// 增量式更新CRC(核心函数)
uint16_t crc16_ccitt_update(uint16_t crc_prev, uint8_t data)
{
// 将当前字节与CRC高8位异或
crc_prev ^= (uint16_t)data << 8;
// 逐位处理8个bit
for (uint8_t i = 0; i < 8; i++)
{
// 判断最高位是否为1
if (crc_prev & 0x8000)
{
// 左移1位后异或多项式
crc_prev = (crc_prev << 1) ^ CRC16_CCITT_POLY;
}
else
{
// 仅左移1位
crc_prev <<= 1;
}
}
return crc_prev;
}
// 完整数据CRC计算
uint16_t crc16_ccitt_calculate(const uint8_t *data, size_t length)
{
// 入参校验:空指针/长度0直接返回初始值
if (data == NULL || length == 0)
{
return 0xFFFF;
}
// 初始化CRC为蓝牙标准初始值
uint16_t crc = 0xFFFF;
// 遍历所有字节计算
for (size_t i = 0; i < length; i++)
{
crc = crc16_ccitt_update(crc, data[i]);
}
// 返回最终CRC值(无反转、无异或)
return crc;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef __CRC_H__
#define __CRC_H__
#include "config.h"
#include <stdint.h>
#include <stdio.h>
uint16_t crc16_ccitt_update(uint16_t crc_prev, uint8_t data);
uint16_t crc16_ccitt_calculate(const uint8_t *data, size_t length);
#endif
+1568
View File
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
#ifndef _BLE_H_
#define _BLE_H_
#include "esp_err.h"
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef __cplusplus
extern "C" {
#endif
/* NimBLE 在 ble_hs.c 中实现,公开 host/ble_hs.h 未导出 */
void ble_hs_lock(void);
void ble_hs_unlock(void);
extern const char *BLE_TAG;
extern bool notify_enabled;
extern volatile bool sentinel_mode_is_active;
extern char s_emoji_delete_target_pack;
extern TaskHandle_t notification_task_handle;
extern TaskHandle_t sentinel_task_handle;
// 设备名称
#define DEVICE_NAME "咔宝"
#define MAX_NOTIFY_LEN 256
// BLE 特征值
#define BLE_SERVICE_UUID 0xFFFF
#define BLE_CHAR_UUID_TX 0xFF01 // ESP32发送,APP接收
#define BLE_CHAR_UUID_RX 0xFF02 // APP发送,ESP32接收
#define BLE_CHAR_UUID_NOTIFY 0xFF03 // 通知特征
#define SENTINEL_TASK_PRIORITY 5
#define SENTINEL_TURN_ON 0x01
#define SENTINEL_TURN_OFF 0x02
#define SENTINEL_LISTEN_START_PULSE 0x04
#define SENTINEL_LISTEN_STOP_PULSE 0x08
#define SENTINEL_PULSE_TIMEOUT_MS 3000
#define SENTINEL_TOTAL_DURATION_MS (20 * 60 * 1000)
#define BLE_SEND_PROGRESS (1<<0)
#define BLE_SEND_VERSION (1<<1)
#define BLE_SEND_DL_EMOJI_FAILED (1<<2)
#define BLE_SEND_DL_EMOJI_SUCCESS (1<<3)
#define BLE_SEND_DOWNLOADED_PACKS (1<<4)
#define BLE_SEND_WIFI_STATE (1<<5)
#define BLE_SEND_EMOJI_USING (1<<6)
#define BLE_SEND_EMOJI_EXCHANGE_RESULT (1<<7)
#define BLE_SEND_OTA_RESULT (1<<8)
#define BLE_SEND_EMOJI_DELETE_RESULT (1<<9)
#define BLE_SEND_EMOJI_CLEAR_RESULT (1<<10)
/** 仅发送 Feedback 2.2A brightness 单帧(如设置页亮度滑条松手),由 notification_task 发送 */
#define BLE_SEND_BRIGHTNESS (1<<11)
/** 表情包下载成功后,等待首次切换指令做“收尾”(不重启,停留在目标包) */
extern volatile bool g_emoji_upgrade_pending_finalize;
/** 本次下载成功的 pack('A'/'B'/'C'/'D',或 '0') */
extern volatile char g_emoji_upgrade_downloaded_pack;
/** 最近一次收到的“切换表情包”目标 pack(供收尾逻辑判断) */
extern volatile char g_emoji_upgrade_last_switch_target;
/** 请求通过 BLE 发送 OTA 升级进度 1.11_B(0~100),由 notification_task 实际发送 */
void ble_send_ota_progress(uint8_t progress);
/** 请求通过 BLE 发送 OTA 升级结果 1.11_A(0=失败,1=成功),由 notification_task 实际发送 */
void ble_send_ota_result_request(uint8_t success);
/** 设置本次「切换表情包」指令的目标 Pack_id,供 notification_task 发送切换结果前执行保存 */
void set_emoji_exchange_target_pack(char pack_id);
/** 开始 BLE 广播(GAP 回调等 NimBLE 上下文可直接调用) */
void ble_app_advertise(void);
/** 整机初始化完成后首次起广播:从 app_main 等任务调用,内部持 ble_hs_lock */
void ble_app_advertise_after_system_init(void);
/** 设置本次「删除表情包」指令的目标 Pack_id,供 notification_task 发送删除结果前执行删除 */
void ble_set_emoji_delete_target(char pack_id);
void ble_main(void);
void send_notification_to_app(const char *message);
/** 请求通过 BLE 发送 WiFi 状态反馈(连接成功 0x01 / 断开 0x00),由 notification_task 实际发送 */
void ble_send_wifi_state_request(uint8_t connected);
void send_protocol_frame(const uint8_t *data, size_t len);
bool ble_is_connected(void);
#ifdef __cplusplus
}
#endif
#endif /* _BLE_H_ */
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
#ifndef __BLE_ANSY_H__
#define __BLE_ANSY_H__
#include "config.h"
#include <stdint.h>
#include <stdio.h>
#define play_sticker (uint16_t)0x0001
#define sentinel_mode (uint16_t)0x0002
#define exit_all_modes (uint16_t)0x0003
#define set_brightness (uint16_t)0x0004
#define date_info (uint16_t)0x0005
#define time_info (uint16_t)0x0006
#define city_info (uint16_t)0x0007
#define w_t_info (uint16_t)0x0008
#define wifi_config (uint16_t)0x0009
#define ota_request (uint16_t)0x000A
#define emoji_pack_download (uint16_t)0x000B
#define ota_cancel (uint16_t)0x000C /* 1.13 OTA 升级取消 */
#define speed_gear (uint16_t)0x000D /* V2 车机→设备:档位(1)+速度(2BE,KM/h),见 ble_data_ansy */
#define get_sys_info (uint16_t)0x0010
#define emojo_cmd (uint16_t)0x0020
#define clear_emojo (uint16_t)0x0021
#define reboot (uint16_t)0x0022 /* 重启设备 */
int ble_data_ansy(uint8_t *input, size_t length);
int ble_wv_data_ansy2(uint8_t *input, size_t length);
/** 1.12 表情包下载:获取本次要下载的目标系列 (A/B/C/D),由指令 0x000B 的 Target_pack 解析得到 */
char ble_get_emoji_download_target_pack(void);
/**
* UI 页面创建完成后调用:回放在 UI 未就绪阶段缓存的日期/时间。
*/
void ble_datetime_flush_cached_to_ui(void);
/**
* UI 页面创建完成后调用:回放在 UI 未就绪阶段缓存的天气数据(当前/+2h/+4h)。
*/
void ble_weather_flush_cached_to_ui(void);
#endif
+588
View File
@@ -0,0 +1,588 @@
#include "ble_send.h"
#include "ble.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "config.h"
#include "esp_log.h"
#include "ALGORITHM/crc.h"
#include "EMOJI_STORAGE/emoji_storage.h"
#include "event.h"
/* 统一亮度真源(定义在 main.c) */
extern int get_system_brightness_percent(void);
volatile uint8_t s_ota_progress_to_send = 0;
volatile bool g_emoji_upgrade_pending_finalize = false;
volatile char g_emoji_upgrade_downloaded_pack = '0';
volatile char g_emoji_upgrade_last_switch_target = '0';
static uint8_t s_wifi_state_to_send = 0;
bool g_wifi_state = 0;
static uint8_t s_ota_result_to_send = 0;
static char s_emoji_switch_target_pack = '0';
volatile uint8_t g_brightness = 85;
void notification_task(void *arg)//上报任务
{
ESP_LOGI(BLE_TAG, "通知任务启动");
uint32_t bits = 0;
BaseType_t rec;
while (1)
{
rec = xTaskNotifyWait(0, ULONG_MAX, &bits, portMAX_DELAY);//永久等待通知
ESP_LOGI("NOTIFICATION", "recv notify");
if (rec != pdPASS)
{
ESP_LOGE("NOTIFICATION", "接受任务通知失败");
continue;
}
if (bits & BLE_SEND_PROGRESS)//BLE 发送OTA 升级进度
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_PROGRESS, 发送OTA进度(十进制)=%d%%", (int)s_ota_progress_to_send);
notify_enabled = 1;
send_ota_progress_frame(s_ota_progress_to_send);
}
else if (bits & BLE_SEND_OTA_RESULT)//返回OTA 升级结果
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_OTA_RESULT, 发送OTA结果 %s", s_ota_result_to_send ? "成功" : "失败");
notify_enabled = 1;
send_ota_result_frame(s_ota_result_to_send);
}
else if (bits & BLE_SEND_VERSION) // 收到 get_system_info (0x0010) 请求后发送版本号、亮度和系统状态
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_VERSION (get_system_info 请求)");
int current_brightness_percent = get_system_brightness_percent();
if (current_brightness_percent < 0)
current_brightness_percent = 0;
if (current_brightness_percent > 100)
current_brightness_percent = 100;
g_brightness = (uint8_t)current_brightness_percent; /* 保持兼容镜像 */
notify_enabled = 1;
// 发送3个协议反馈帧
// feedback2.1A: get_system_ok
send_get_system_ok_frame();
vTaskDelay(pdMS_TO_TICKS(10)); // 短暂延时,避免发送过快
// feedback2.2A: brightness
send_brightness_frame((uint8_t)current_brightness_percent);
vTaskDelay(pdMS_TO_TICKS(10));
// feedback2.3A: version
send_version_frame(DEVICE_FIRMWARE_VERSION);
vTaskDelay(pdMS_TO_TICKS(10));
// 1.10_A: 返回当前 WiFi 状态(配网成功为 0x01,未连为 0x00)
send_wifi_state_frame(s_wifi_state_to_send);
ESP_LOGI(BLE_TAG, "已发送get_system_info反馈: version=%s, 亮度=%u, WiFi状态=%s", DEVICE_FIRMWARE_VERSION, (unsigned int)g_brightness, s_wifi_state_to_send ? "已连接" : "断开");
}
else if (bits & BLE_SEND_BRIGHTNESS)
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_BRIGHTNESS");
int current_brightness_percent = get_system_brightness_percent();
if (current_brightness_percent < 0)
current_brightness_percent = 0;
if (current_brightness_percent > 100)
current_brightness_percent = 100;
g_brightness = (uint8_t)current_brightness_percent;
notify_enabled = 1;
send_brightness_frame((uint8_t)current_brightness_percent);
}
else if (bits & BLE_SEND_DL_EMOJI_FAILED)//返回下载表情失败
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DL_EMOJI_FAILED");
notify_enabled = 1;
send_emoji_download_result_frame(0);
}
else if (bits & BLE_SEND_DL_EMOJI_SUCCESS)//返回下载表情成功
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DL_EMOJI_SUCCESS");
notify_enabled = 1;
send_emoji_download_result_frame(1);
}
else if (bits & BLE_SEND_DOWNLOADED_PACKS)//返回已下载表情包列表
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DOWNLOADED_PACKS");
notify_enabled = 1;
send_downloaded_packs_frame();
}
else if (bits & BLE_SEND_WIFI_STATE)//返回WiFi状态
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_WIFI_STATE, 主动发送 1.10_A WiFi状态=%s", s_wifi_state_to_send ? "已连接(0x01)" : "断开(0x00)");
notify_enabled = 1;
send_wifi_state_frame(s_wifi_state_to_send);
}
else if (bits & BLE_SEND_EMOJI_EXCHANGE_RESULT)//返回表情包切换结果
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_EXCHANGE_RESULT");
notify_enabled = 1;
esp_err_t err = emoji_storage_save_using_pack(s_emoji_switch_target_pack);
send_emoji_switch_result_frame(err == ESP_OK ? 1 : 0);
ESP_LOGI("NOTIFICATION", "g_emoji_upgrade_pending_finalize = %d", (int)g_emoji_upgrade_pending_finalize);
if (g_emoji_upgrade_pending_finalize)
{
/* 下载成功后第一次切换:收尾升级(不重启,不回到原来) */
g_emoji_upgrade_pending_finalize = false;
g_emoji_upgrade_last_switch_target = s_emoji_switch_target_pack;
set_system_event(EMOJI_UPGRADE_FINALIZE_BIT);
}
}
else if (bits & BLE_SEND_EMOJI_USING)//返回正在使用的表情包
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_USING");
notify_enabled = 1;
send_emoji_using_frame(emoji_storage_get_using_pack());
}
else if (bits & BLE_SEND_EMOJI_DELETE_RESULT)//返回删除表情包结果
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_DELETE_RESULT");
notify_enabled = 1;
emoji_storage_remove_pack_files(s_emoji_delete_target_pack);
esp_err_t err = emoji_storage_remove_downloaded_pack(s_emoji_delete_target_pack);
send_emoji_delete_result_frame(err == ESP_OK ? 1 : 0);
}
else if (bits & BLE_SEND_EMOJI_CLEAR_RESULT)
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_CLEAR_RESULT");
notify_enabled = 1;
esp_err_t err = emoji_storage_clear_all_packs();
send_emoji_clear_result_frame(err == ESP_OK ? 1 : 0);
}
else
{
}
}
}
/* Feedback 3.4 返回删除表情包结果 0x0020 op=0x03:帧头(1)+类(2)+指令(2)+op(1)+Exchange_result(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_delete_result_frame(uint8_t success)
{
uint8_t frame[10] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x03; /* op=0x03 删除结果 */
frame[6] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 7);
frame[7] = (crc16 >> 8) & 0xFF;
frame[8] = crc16 & 0xFF;
frame[9] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.4删除表情包结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback clear_emojo 返回清除所有表情包结果 0x0021:帧头(1)+类(2)+指令(2)+result(1)+CRC16(2)+帧尾(1)=9 */
void send_emoji_clear_result_frame(uint8_t success)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x21;
frame[5] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送清除所有表情包结果: %s, CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 表情包查询/切换 0x0020 op=0x02 切换结果:帧头(1)+类(2)+指令(2)+op(1)+result(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_switch_result_frame(uint8_t success)
{
uint8_t frame[11] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x02; /* op=0x02 切换结果 */
frame[6] = success ? 0x01 : 0x00;
frame[7] = s_emoji_switch_target_pack;//pack_id
crc16 = crc16_ccitt_calculate(frame, 8);
frame[8] = (crc16 >> 8) & 0xFF;
frame[9] = crc16 & 0xFF;
frame[10] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.2切换结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 表情包下载结果 0x0003:帧头(1)+类(2)+指令(2)+result(1)+CRC16(2)+帧尾(1)=9 */
void send_emoji_download_result_frame(uint8_t success)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x03; /* 表情包下载结果 */
frame[5] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送表情包下载结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 0x0020 op=0x00 返回已安装表情包列表:帧头(1)+类(2)+指令(2)+op(1)+count(1)+[id1..idN]+CRC16(2)+帧尾(1);默认系列 0 自带,无论如何都先发 */
#define EMOJI_PACK_LIST_MAX 16
void send_downloaded_packs_frame(void)
{
char list_buf[64];
int len = emoji_storage_get_downloaded_packs(list_buf, sizeof(list_buf));
uint8_t ids[EMOJI_PACK_LIST_MAX];
int count = 0;
/* 默认表情包系列 0 始终第一位 */
ids[count++] = (uint8_t)'0';
if (len > 0)
{
for (int i = 0; i < len && count < EMOJI_PACK_LIST_MAX; i++)
{
if (list_buf[i] != ',' && list_buf[i] != '0')
{
ids[count++] = (uint8_t)list_buf[i];
}
}
}
size_t frame_len = 10 + count;
uint8_t *frame = (uint8_t *)malloc(frame_len);
if (!frame)
{
ESP_LOGE(BLE_TAG, "已安装列表帧分配失败");
return;
}
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x00; /* op=0x00 返回已安装列表 */
frame[6] = (uint8_t)count;
for (int i = 0; i < count; i++)
{
frame[7 + i] = ids[i];
}
uint16_t crc16 = crc16_ccitt_calculate(frame, 7 + count);
frame[7 + count] = (crc16 >> 8) & 0xFF;
frame[8 + count] = crc16 & 0xFF;
frame[9 + count] = 0x55;
send_protocol_frame(frame, frame_len);
ESP_LOGI(BLE_TAG, "发送已安装表情包列表: count=%d, 发送 CRC16=0x%04X", count, crc16);
free(frame);
}
/* 发送get_system_info反馈帧2.3A: version */
void send_version_frame(const char *version_str)
{
size_t version_len = strlen(version_str);
size_t frame_len = 7 + version_len + 2 + 1; // 帧头(1) + 类(2) + 指令(2) + 长度(2) + 版本号 + CRC16(2) + 帧尾(1)
uint8_t *frame = (uint8_t *)malloc(frame_len);
if (!frame)
{
ESP_LOGE(BLE_TAG, "分配内存失败");
return;
}
uint16_t crc16;
size_t pos = 0;
// 帧头
frame[pos++] = 0xAA;
// 类 (Feedback)
frame[pos++] = 0xFF;
frame[pos++] = 0xFF;
// 指令 (version_cmd)
frame[pos++] = 0x00;
frame[pos++] = 0x06;
// 长度 (2字节,大端序)
frame[pos++] = (version_len >> 8) & 0xFF;
frame[pos++] = version_len & 0xFF;
// 版本号字符串
memcpy(&frame[pos], version_str, version_len);
pos += version_len;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, pos);
frame[pos++] = (crc16 >> 8) & 0xFF;
frame[pos++] = crc16 & 0xFF;
// 帧尾
frame[pos++] = 0x55;
send_protocol_frame(frame, frame_len);
ESP_LOGI(BLE_TAG, "发送version帧: %s (长度: %d), 发送 CRC16=0x%04X", version_str, frame_len, crc16);
free(frame);
}
/* 1.11_B: 发送 OTA 升级进度 Feedback OTA_progress_cmd 0x0002,进度 0~100 */
void send_ota_progress_frame(uint8_t progress)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x02;
frame[5] = progress > 100 ? 100 : progress;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送OTA进度帧 1.11_B: 进度(十进制)=%d%%, 发送 CRC16=0x%04X", (int)frame[5], crc16);
}
/* 1.11_A: 发送 OTA 升级结果 Feedback OTA_result_cmd 0x0001,0x01=成功 0x00=失败 */
void send_ota_result_frame(uint8_t result)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x01;
frame[5] = result ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送OTA结果帧 1.11_A: %s, 发送 CRC16=0x%04X", result ? "成功(0x01)" : "失败(0x00)", crc16);
}
void ble_send_ota_progress(uint8_t progress)
{
s_ota_progress_to_send = progress > 100 ? 100 : progress;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_PROGRESS, eSetBits);
}
}
void ble_send_ota_result_request(uint8_t success)
{
s_ota_result_to_send = success ? 1 : 0;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_OTA_RESULT, eSetBits);
}
else
{
ESP_LOGE(BLE_TAG, "notification_task_handle 不存在");
}
}
/* 发送get_system_info反馈帧2.1A: get_system_ok */
void send_get_system_ok_frame(void)
{
uint8_t frame[9] = {0};
uint16_t crc16;
// 帧头
frame[0] = 0xAA;
// 类 (Feedback)
frame[1] = 0xFF;
frame[2] = 0xFF;
// 指令 (get_sysinfo_feedback)
frame[3] = 0x00;
frame[4] = 0x04;
// 指令返回 (get_system_ok = 0x01)
frame[5] = 0x01;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
// 帧尾
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送get_system_ok帧, 发送 CRC16=0x%04X", crc16);
}
/* 发送get_system_info反馈帧2.2A: brightness */
void send_brightness_frame(uint8_t brightness)
{
uint8_t frame[9] = {0};
uint16_t crc16;
// 帧头
frame[0] = 0xAA;
// 类 (Feedback)
frame[1] = 0xFF;
frame[2] = 0xFF;
// 指令 (Brightness_cmd)
frame[3] = 0x00;
frame[4] = 0x05;
frame[5] = brightness > 100 ? 100 : brightness;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
// 帧尾
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送brightness帧: 亮度(十进制)=%d, 发送 CRC16=0x%04X", (int)frame[5], crc16);
}
/* Feedback 3.2: 返回正在使用的表情包 帧头(1)+类(2)+指令(2)+Check_using(1)+Pack_id(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_using_frame(uint8_t pack_id)
{
uint8_t frame[10] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20; /* Emoji_cmd 0x0020 */
frame[5] = 0x01; /* Check_using 查询正在使用 */
frame[6] = pack_id; /* Pack_id ASCII 1 byte */
crc16 = crc16_ccitt_calculate(frame, 7);
frame[7] = (crc16 >> 8) & 0xFF;
frame[8] = crc16 & 0xFF;
frame[9] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.2正在使用表情包: Pack_id=%c (0x%02X), 发送 CRC16=0x%04X", (char)pack_id, pack_id, crc16);
}
/* 发送 WiFi 状态反馈帧 1.10_A: Feedback Wifi_state_cmd 0x0010 */
void send_wifi_state_frame(uint8_t state)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x10;
frame[5] = state ? 0x01 : 0x00; /* 0x01=已连接, 0x00=断开 */
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送WiFi状态帧: %s, 发送 CRC16=0x%04X", state ? "已连接" : "断开", crc16);
}
void ble_send_wifi_state_request(uint8_t connected)
{
s_wifi_state_to_send = connected ? 1 : 0;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_WIFI_STATE, eSetBits);
}
}
void ble_request_send_brightness(void)
{
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_BRIGHTNESS, eSetBits);
}
}
/* 切换表情包指令的目标 Pack_id,由 ble_data_ansy 在收到 op=0x02 时设置 */
// static char s_emoji_switch_target_pack = '0';
void ble_set_emoji_switch_target(char pack_id)
{
s_emoji_switch_target_pack = pack_id;
}
/* 删除表情包指令的目标 Pack_id,由 ble_data_ansy 在收到 op=0x03 时设置 */
void ble_set_emoji_delete_target(char pack_id)
{
s_emoji_delete_target_pack = pack_id;
}
void robot_sleep_frame(uint8_t state)
{
(void)state;
uint8_t frame[8] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x07;
crc16 = crc16_ccitt_calculate(frame, 5);
frame[5] = (crc16 >> 8) & 0xFF;
frame[6] = crc16 & 0xFF;
frame[7] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送robot睡眠状态帧: 发送 CRC16=0x%04X", crc16);
}
void robot_wake_frame(uint8_t state)
{
(void)state;
uint8_t frame[8] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x08;
crc16 = crc16_ccitt_calculate(frame, 5);
frame[5] = (crc16 >> 8) & 0xFF;
frame[6] = crc16 & 0xFF;
frame[7] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送robot唤醒状态帧: 发送 CRC16=0x%04X", crc16);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef __BLE_SEND_H__
#define __BLE_SEND_H__
#include <stdint.h>
#include <stdio.h>
extern volatile uint8_t g_brightness;
void send_emoji_delete_result_frame(uint8_t success);
void send_emoji_clear_result_frame(uint8_t success);
void send_emoji_switch_result_frame(uint8_t success);
void send_emoji_download_result_frame(uint8_t success);
void send_downloaded_packs_frame(void);
void send_version_frame(const char *version_str);
void send_ota_progress_frame(uint8_t progress);
void send_ota_result_frame(uint8_t result);
void ble_send_ota_progress(uint8_t progress);
void ble_send_ota_result_request(uint8_t success);
void send_get_system_ok_frame(void);
void send_brightness_frame(uint8_t brightness);
void send_emoji_using_frame(uint8_t pack_id);
void send_wifi_state_frame(uint8_t state);
void ble_send_wifi_state_request(uint8_t connected);
/** 请求发送当前亮度单帧(0x0005),由 notification_task 调用 send_protocol_frame */
void ble_request_send_brightness(void);
void ble_set_emoji_switch_target(char pack_id);
void ble_set_emoji_delete_target(char pack_id);
void send_version_frame(const char *version_str);
void send_ota_progress_frame(uint8_t progress);
void send_ota_result_frame(uint8_t result);
void robot_sleep_frame(uint8_t state);
void robot_wake_frame(uint8_t state);
void notification_task(void *arg);
#endif
+121
View File
@@ -0,0 +1,121 @@
# 1. 自动搜索 ui 目录下所有的 .c 文件 (包含子目录)
file(GLOB_RECURSE GUI_SOURCES "${CMAKE_CURRENT_LIST_DIR}/ui/*.c")
file(GLOB GUIDER_FONT_SOURCES "${CMAKE_CURRENT_LIST_DIR}/ui/generated/guider_fonts/*.c")
# 下列文件已在 SRCS 中显式列出,避免 GLOB 与显式重复导致同一 .c 编译/链接两次
list(REMOVE_ITEM GUI_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_events.c"
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_nav.c"
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_test_page.c"
)
# 注释内置 guider_fonts:改为运行时外部字体
list(REMOVE_ITEM GUI_SOURCES ${GUIDER_FONT_SOURCES})
# 2. 定义头文件路径
set(GUI_INCLUDES
"ui/custom"
"ui/generated"
"ui/generated/guider_customer_fonts"
)
# 3. 注册组件
idf_component_register(
SRCS
"main.c"
"OTA/ota_upgrade.c"
"ota_trigger.c"
"MQTT/mqtt.c"
"FLASH/flash.c"
"ZIP_EXTRACTOR/zip_extractor.c"
"MINIZ/miniz.c"
"MOTOR/motor.c"
"MOTOR/step_motor_init.c"
"MOTOR/foc.c"
"MOTOR/motor_timer.c"
"MOTOR/motor_control.c"
"MOTOR/motor_io.c"
"BLE/ble.c"
"BLE/ble_data_ansy.c"
"BLE/ble_send.c"
"WIFI/wifi.c"
"SC7U22/SC7U22.c"
"EVENT/event.c"
"ES7243E/ES7243E.c"
"SPEECH/speech_app.c"
"SPEECH/speech_init.c"
"SCREEN/screen_init.c"
"SCREEN/screen_brightness.c"
"SCREEN/screen_sleep.c"
"UI_DISPATCH/ui_dispatch.c"
"PET/pet_init.c"
"NS4168/ns4168.c"
"NS4168/wav_demux.c"
"NS4168/ogg_demux.c"
"NS4168/micro_decoder_wrapper.cpp"
"NS4168/AudioSourceFile.cpp"
"UI_DISPATCH/almanac_service.c"
"ui_cmd.c"
"ALGORITHM/crc.c"
"SYSTEM/system.c"
"EMOJI_STORAGE/emoji_storage.c"
"LVGL/lvgl_src.c"
"LVGL/lvgl_init.c"
"LVGL/lvgl_lottie.c"
"LVGL/lvgl_lottie_logic.c"
"remapping.c"
"ui/ui_events.c"
"ui/ui_nav.c"
"ui/ui_test_page.c"
${GUI_SOURCES}
INCLUDE_DIRS
"."
"EVENT"
"ui"
${GUI_INCLUDES}
EMBED_TXTFILES
"UI_DISPATCH/yiji_data_2026_2030.json"
REQUIRES
# --- GUI & Screen ---
lvgl
esp_lcd_qspi_amoled
esp_lcd_touch_cst820
# --- 系统基础 ---
nvs_flash
esp_event
esp_wifi
esp_netif
driver
log
freertos
spi_flash
# --- 网络与通信 ---
mqtt
esp_http_client
esp_https_ota
bt
# --- 存储与文件系统 ---
app_update
esp_partition
fatfs
spiffs
sdmmc
# --- 其他 ---
json
mbedtls
)
# 内置 guider_fonts 已不参与编译,保留以下属性配置作为历史参考
# set_source_files_properties(
# ${GUIDER_FONT_SOURCES}
# PROPERTIES COMPILE_OPTIONS
# -DLV_LVGL_H_INCLUDE_SIMPLE
# )
# 5. 生成分区镜像(storage 已废弃,表情已迁移至 /extflash)
# spiffs_create_partition_image(storage lottie_files FLASH_IN_PROJECT)
+638
View File
@@ -0,0 +1,638 @@
#include "emoji_storage.h"
#include "FLASH/flash.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "cjson.h"
static const char *TAG = "emoji_storage";
// NVS命名空间
#define NVS_NAMESPACE "emoji_config"//"emoji_config"
#define USING_PACK_KEY "using_pack"
/* NVS key 最大 15 字符,原 "downloaded_packs" 超长导致 ESP_ERR_NVS_KEY_TOO_LONG */
#define DOWNLOADED_PACKS_KEY "dl_packs"
#define EXTFLASH_BASE "/extflash"
// 内存缓存
static char g_using_pack = '0'; // 默认表情包
static char g_downloaded_packs[256] = {0}; // 存储JSON格式的已下载列表
/**
* @brief 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表并写回 NVS
* 解决“昨天下载了 A 但当时 NVS key 超长保存失败、查询只返回 0”的问题
*/
static void emoji_storage_sync_downloaded_packs_from_fs(void)
{
extflash_lock();
DIR *dir = opendir(EXTFLASH_BASE);
if (!dir) {
extflash_unlock();
return;
}
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (!json_array || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
if (!json_array) {
closedir(dir);
extflash_unlock();
return;
}
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
}
bool changed = false;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
if (entry->d_name[1] != '\0') {
continue;
}
char pack_id = entry->d_name[0];
if (pack_id != 'A' && pack_id != 'B' && pack_id != 'C' && pack_id != 'D') {
continue;
}
char path[32];
snprintf(path, sizeof(path), "%s/%c", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
continue;
}
bool found = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == pack_id) {
found = true;
break;
}
}
if (!found) {
char pack_str[2] = {pack_id, '\0'};
cJSON_AddItemToArray(json_array, cJSON_CreateString(pack_str));
changed = true;
ESP_LOGI(TAG, "从 /extflash 同步到已下载列表: %c", pack_id);
}
}
closedir(dir);
extflash_unlock();
/* 已安装列表始终包含默认表情包系列 0(内置) */
{
bool has_zero = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == '0') {
has_zero = true;
break;
}
}
if (!has_zero) {
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
changed = true;
}
}
if (!changed) {
cJSON_Delete(json_array);
return;
}
char *json_string = cJSON_PrintUnformatted(json_array);
cJSON_Delete(json_array);
if (!json_string) {
return;
}
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
nvs_handle_t nvs_handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) != ESP_OK) {
return;
}
if (nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs) == ESP_OK) {
nvs_commit(nvs_handle);
ESP_LOGI(TAG, "已下载列表已同步到 NVS: %s", g_downloaded_packs);
}
nvs_close(nvs_handle);
}
/**
* @brief 从NVS加载表情包信息到内存
*/
esp_err_t emoji_storage_load(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
size_t required_size;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "打开NVS命名空间失败: %s,使用默认值", esp_err_to_name(err));
g_using_pack = '0';
strcpy(g_downloaded_packs, "[\"0\"]"); // 默认只有'0'
return err;
}
// 读取正在使用的表情包
char using_pack_str[2] = {0};
required_size = sizeof(using_pack_str);
err = nvs_get_str(nvs_handle, USING_PACK_KEY, using_pack_str, &required_size);
if (err == ESP_OK && required_size > 0) {
g_using_pack = using_pack_str[0];
ESP_LOGI(TAG, "加载正在使用的表情包: %c", g_using_pack);
} else {
ESP_LOGW(TAG, "读取using_pack失败: %s,使用默认值'0'", esp_err_to_name(err));
g_using_pack = '0';
}
// 读取已下载的表情包列表
required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err == ESP_OK && required_size > 0) {
ESP_LOGI(TAG, "加载已下载表情包列表: %s", g_downloaded_packs);
} else {
strcpy(g_downloaded_packs, "[\"0\"]");
}
nvs_close(nvs_handle);
/* 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表,避免“昨天下了 A 但 NVS 未保存”只返回 0 */
emoji_storage_sync_downloaded_packs_from_fs();
return ESP_OK;
}
/**
* @brief 保存当前使用的表情包ID到NVS
*/
esp_err_t emoji_storage_save_using_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_save_using_pack('%c') 被调用, 当前 g_using_pack='%c'", pack_id, g_using_pack);
nvs_handle_t nvs_handle;
esp_err_t err;
char pack_str[2] = {pack_id, '\0'};
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
err = nvs_set_str(nvs_handle, USING_PACK_KEY, pack_str);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 保存 using_pack 到 NVS 失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
g_using_pack = pack_id;
ESP_LOGI(TAG, "[EMOJI_DEBUG] 成功保存 using_pack='%c' 到 NVS, g_using_pack 已更新", pack_id);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 添加已下载的表情包ID到列表并保存到NVS
*/
esp_err_t emoji_storage_add_downloaded_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_add_downloaded_pack('%c') 被调用", pack_id);
ESP_LOGI(TAG, "[EMOJI_DEBUG] 当前 g_using_pack='%c', g_downloaded_packs=%s", g_using_pack, g_downloaded_packs);
nvs_handle_t nvs_handle;
esp_err_t err;
cJSON *json_array = NULL;
cJSON *item = NULL;
char pack_str[2] = {pack_id, '\0'};
bool found = false;
if (emoji_storage_is_downloaded(pack_id)) {
ESP_LOGW(TAG, "[EMOJI_DEBUG] 表情包 %c 已存在于下载列表中, 直接返回 ESP_OK", pack_id);
return ESP_OK;
}
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 读取现有的JSON数组
size_t required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err != ESP_OK) {
// 如果不存在,创建新数组
json_array = cJSON_CreateArray();
} else {
// 解析现有JSON
json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
// 解析失败,创建新数组
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
}
}
// 检查是否已存在
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *existing_item = cJSON_GetArrayItem(json_array, i);
if (existing_item && cJSON_IsString(existing_item)) {
if (existing_item->valuestring[0] == pack_id) {
found = true;
break;
}
}
}
// 如果不存在,添加到数组
if (!found) {
item = cJSON_CreateString(pack_str);
cJSON_AddItemToArray(json_array, item);
ESP_LOGI(TAG, "添加表情包 %c 到下载列表", pack_id);
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 获取当前使用的表情包ID
*/
char emoji_storage_get_using_pack(void)
{
return g_using_pack;
}
/**
* @brief 获取已下载的表情包列表(JSON格式字符串)
*/
int emoji_storage_get_downloaded_packs_json(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
int len = strlen(g_downloaded_packs);
if (len >= max_len) {
ESP_LOGW(TAG, "缓冲区太小,需要 %d 字节,但只有 %d 字节", len + 1, max_len);
return -1;
}
strncpy(packs, g_downloaded_packs, max_len - 1);
packs[max_len - 1] = '\0';
return len;
}
/**
* @brief 获取已下载的表情包列表(逗号分隔字符串)
*/
int emoji_storage_get_downloaded_packs(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
return -1;
}
int array_size = cJSON_GetArraySize(json_array);
int pos = 0;
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] != '\0') {
if (pos > 0 && pos < max_len - 1) {
packs[pos++] = ',';
}
if (pos < max_len - 1) {
packs[pos++] = item->valuestring[0];
}
}
}
packs[pos] = '\0';
cJSON_Delete(json_array);
return pos;
}
/**
* @brief 检查指定表情包是否已下载
*/
bool emoji_storage_is_downloaded(char pack_id)
{
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
return false;
}
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_Delete(json_array);
return true;
}
}
}
cJSON_Delete(json_array);
return false;
}
/**
* @brief 从列表中删除指定的表情包
*/
esp_err_t emoji_storage_remove_downloaded_pack(char pack_id)
{
// 不允许删除默认表情包'0'
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'");
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
nvs_close(nvs_handle);
return ESP_ERR_INVALID_STATE;
}
// 查找并删除
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_DeleteItemFromArray(json_array, i);
ESP_LOGI(TAG, "从下载列表删除表情包: %c", pack_id);
break;
}
}
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
if (g_using_pack == pack_id)
{
ESP_LOGW(TAG, "被删除的表情包 '%c' 正是当前使用中的,重置为默认 '0'", pack_id);
emoji_storage_save_using_pack('0');
}
return ESP_OK;
}
/**
* @brief 递归删除指定目录及其内容
*/
static esp_err_t remove_directory_recursive(const char *path)
{
DIR *dir = opendir(path);
if (!dir) {
ESP_LOGW(TAG, "无法打开目录: %s", path);
return ESP_ERR_NOT_FOUND;
}
struct dirent *entry;
char filepath[512];
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
snprintf(filepath, sizeof(filepath), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(filepath, &st) != 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
remove_directory_recursive(filepath);
} else {
if (unlink(filepath) != 0) {
ESP_LOGW(TAG, "删除文件失败: %s", filepath);
}
}
}
closedir(dir);
if (rmdir(path) != 0) {
ESP_LOGW(TAG, "删除目录失败: %s", path);
return ESP_FAIL;
}
ESP_LOGI(TAG, "已删除目录: %s", path);
return ESP_OK;
}
/**
* @brief 删除指定表情包的文件(目录 + zip)
*/
esp_err_t emoji_storage_remove_pack_files(char pack_id)
{
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'的文件");
return ESP_ERR_INVALID_ARG;
}
char dir_path[32];
char zip_path[32];
snprintf(dir_path, sizeof(dir_path), "%s/%c", EXTFLASH_BASE, pack_id);
snprintf(zip_path, sizeof(zip_path), "%s/%c.zip", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(dir_path, &st) == 0 && S_ISDIR(st.st_mode)) {
remove_directory_recursive(dir_path);
}
if (stat(zip_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
if (unlink(zip_path) == 0) {
ESP_LOGI(TAG, "已删除zip文件: %s", zip_path);
} else {
ESP_LOGW(TAG, "删除zip文件失败: %s", zip_path);
}
}
return ESP_OK;
}
/**
* @brief 清除所有已下载的表情包(删除文件 + 清空NVS + 重置为默认表情包)
*/
esp_err_t emoji_storage_clear_all_packs(void)
{
const char packs[] = {'A', 'B', 'C', 'D'};
for (int i = 0; i < (int)(sizeof(packs) / sizeof(packs[0])); i++) {
emoji_storage_remove_pack_files(packs[i]);
}
esp_err_t err = emoji_storage_clear_downloaded_packs();
if (err != ESP_OK) {
ESP_LOGE(TAG, "清空NVS已下载列表失败: %s", esp_err_to_name(err));
return err;
}
if (g_using_pack != '0') {
err = emoji_storage_save_using_pack('0');
if (err != ESP_OK) {
ESP_LOGE(TAG, "重置使用中的表情包失败: %s", esp_err_to_name(err));
}
}
ESP_LOGI(TAG, "已清除所有已下载的表情包,恢复为默认表情包'0'");
return ESP_OK;
}
/**
* @brief 清空已下载的表情包列表(保留默认表情包'0')
*/
esp_err_t emoji_storage_clear_downloaded_packs(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 只保留默认表情包'0'
const char *default_packs = "[\"0\"]";
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, default_packs);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strcpy(g_downloaded_packs, default_packs);
ESP_LOGI(TAG, "清空已下载表情包列表,保留默认表情包'0'");
nvs_close(nvs_handle);
return ESP_OK;
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef __EMOJI_STORAGE_H__
#define __EMOJI_STORAGE_H__
#include "esp_err.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 从NVS加载表情包信息到内存
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_load(void);
/**
* @brief 保存当前使用的表情包ID到NVS
* @param pack_id 表情包ID(字符,如'0', 'A', 'B', 'C', 'D'等)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_save_using_pack(char pack_id);
/**
* @brief 添加已下载的表情包ID到列表并保存到NVS
* @param pack_id 表情包ID(字符,如'0', 'A', 'B', 'C', 'D'等)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_add_downloaded_pack(char pack_id);
/**
* @brief 获取当前使用的表情包ID
* @return char 表情包ID,'0'表示默认表情包
*/
char emoji_storage_get_using_pack(void);
/**
* @brief 获取已下载的表情包列表(JSON格式字符串)
* @param packs 输出缓冲区,存储JSON格式字符串(如"[\"0\",\"A\",\"B\"]")
* @param max_len 缓冲区最大长度
* @return int 实际写入的字符数(不包括'\0'),-1表示失败
*/
int emoji_storage_get_downloaded_packs_json(char *packs, int max_len);
/**
* @brief 获取已下载的表情包列表(逗号分隔字符串)
* @param packs 输出缓冲区,存储逗号分隔字符串(如"0,A,B")
* @param max_len 缓冲区最大长度
* @return int 实际写入的字符数(不包括'\0'),-1表示失败
*/
int emoji_storage_get_downloaded_packs(char *packs, int max_len);
/**
* @brief 检查指定表情包是否已下载
* @param pack_id 表情包ID(字符)
* @return true 已下载,false 未下载
*/
bool emoji_storage_is_downloaded(char pack_id);
/**
* @brief 从列表中删除指定的表情包
* @param pack_id 表情包ID(字符)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_remove_downloaded_pack(char pack_id);
/**
* @brief 清空已下载的表情包列表(保留默认表情包'0')
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_clear_downloaded_packs(void);
/**
* @brief 删除指定表情包的文件(目录 + zip)
* @param pack_id 表情包ID(字符,'A'~'D')
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_remove_pack_files(char pack_id);
/**
* @brief 清除所有已下载的表情包(删除文件 + 清空NVS + 重置为默认表情包)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_clear_all_packs(void);
#ifdef __cplusplus
}
#endif
#endif /* __EMOJI_STORAGE_H__ */
+270
View File
@@ -0,0 +1,270 @@
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
// #include "esp_flash.h"
#include "esp_system.h"
#include "esp_idf_version.h"
// #include "unity.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "ES7243E.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/i2s_std.h"
#include "driver/i2s_tdm.h"
#include "soc/soc_caps.h"
#else
#include "driver/i2s.h"
#endif
#include "driver/i2c_master.h"
//esp_codec_dev_handle_t codec_dev = NULL;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE
#define USE_IDF_I2C_MASTER
#include "driver/i2c_master.h"
#else
#include "driver/i2c.h"
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define I2S_MAX_KEEP SOC_I2S_NUM
typedef struct {
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
} i2s_keep_t;
static i2s_comm_mode_t i2s_in_mode = I2S_COMM_MODE_STD;
static i2s_comm_mode_t i2s_out_mode = I2S_COMM_MODE_STD;
static i2s_keep_t *i2s_keep[I2S_MAX_KEEP];
#endif
static void codec_max_sample(uint8_t *data, int size, int *max_value, int *min_value)
{
int16_t *s = (int16_t *) data;
size >>= 1;
int i = 1, max, min;
max = min = s[0];
while (i < size) {
if (s[i] > max) {
max = s[i];
} else if (s[i] < min) {
min = s[i];
}
i++;
}
*max_value = max;
*min_value = min;
}
#ifdef USE_IDF_I2C_MASTER
// 定义为全局变量,供BMI270使用
i2c_master_bus_handle_t i2c_bus_handle = NULL;
#endif
int ut_i2c_init(uint8_t port)
{
#ifdef USE_IDF_I2C_MASTER
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = port;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
#else
i2c_config_t i2c_cfg = {
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
i2c_cfg.sda_io_num = GPIO_NUM_0;
i2c_cfg.scl_io_num = GPIO_NUM_1;
esp_err_t ret = i2c_param_config(port, &i2c_cfg);
if (ret != ESP_OK) {
return -1;
}
return i2c_driver_install(port, i2c_cfg.mode, 0, 0, 0);
#endif
}
static int ut_i2s_init(uint8_t port)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
if (port >= I2S_MAX_KEEP) {
return -1;
}
// Already installed
if (i2s_keep[port]) {
return 0;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO),
.gpio_cfg ={
.mclk = TEST_BOARD_I2S_MCK_PIN,
.bclk = TEST_BOARD_I2S_BCK_PIN,
.ws = TEST_BOARD_I2S_DATA_WS_PIN,
.dout = -1,
.din = TEST_BOARD_I2S_DATA_IN_PIN,
},
};
i2s_keep[port] = (i2s_keep_t *) calloc(1, sizeof(i2s_keep_t));
if (i2s_keep[port] == NULL) {
return -1;
}
#if SOC_I2S_SUPPORTS_TDM
i2s_tdm_slot_mask_t slot_mask = I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3;
i2s_tdm_config_t tdm_cfg = {
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_MONO, slot_mask),
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000),
.gpio_cfg = {
.mclk = TEST_BOARD_I2S_MCK_PIN,
.bclk = TEST_BOARD_I2S_BCK_PIN,
.ws = TEST_BOARD_I2S_DATA_WS_PIN,
.dout = -1,
.din = TEST_BOARD_I2S_DATA_IN_PIN,
},
};
tdm_cfg.slot_cfg.total_slot = 4;
#endif
int ret = i2s_new_channel(&chan_cfg, &i2s_keep[port]->tx_handle, &i2s_keep[port]->rx_handle);
if(ret == ESP_OK) printf("i2s_new_channel success.\n");
else printf("i2s_new_channel failed.\n");
if (i2s_out_mode == I2S_COMM_MODE_STD) {
ret = i2s_channel_init_std_mode(i2s_keep[port]->tx_handle, &std_cfg);
}
#if SOC_I2S_SUPPORTS_TDM
else if (i2s_out_mode == I2S_COMM_MODE_TDM) {
ret = i2s_channel_init_tdm_mode(i2s_keep[port]->tx_handle, &tdm_cfg);
}
#endif
if(ret == ESP_OK) printf("2 success.\n");
else printf("2 failed.\n");
if (i2s_in_mode == I2S_COMM_MODE_STD) {
ret = i2s_channel_init_std_mode(i2s_keep[port]->rx_handle, &std_cfg);
}
#if SOC_I2S_SUPPORTS_TDM
else if (i2s_in_mode == I2S_COMM_MODE_TDM) {
ret = i2s_channel_init_tdm_mode(i2s_keep[port]->rx_handle, &tdm_cfg);
}
#endif
if(ret == ESP_OK) printf("3 success.\n");
else printf("3 failed.\n");
// For tx master using duplex mode
ret=i2s_channel_enable(i2s_keep[port]->tx_handle);
if(ret == ESP_OK) printf("i2s_channel_enable tx success.\n");
ret=i2s_channel_enable(i2s_keep[port]->rx_handle);
if(ret == ESP_OK) printf("i2s_channel_enable rx success.\n");
#else
i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM,
.dma_buf_count = 2,
.dma_buf_len = 128,
.use_apll = true,
.tx_desc_auto_clear = true,
};
int ret = i2s_driver_install(port, &i2s_config, 0, NULL);
i2s_pin_config_t i2s_pin_cfg = {
.mck_io_num = TEST_BOARD_I2S_MCK_PIN,
.bck_io_num = TEST_BOARD_I2S_BCK_PIN,
.ws_io_num = TEST_BOARD_I2S_DATA_WS_PIN,
.data_out_num = TEST_BOARD_I2S_DATA_OUT_PIN,
.data_in_num = TEST_BOARD_I2S_DATA_IN_PIN,
};
i2s_set_pin(port, &i2s_pin_cfg);
#endif
return ret;
}
extern void i2c_scan_devices(void);
extern i2c_master_bus_handle_t s_i2c_bus;
extern i2c_master_bus_handle_t touch_bus_handle_BMI;
//*****************外部方法*************************** */
esp_codec_dev_handle_t ES7243E_Init(void)
{
//为编解码器设备的控制和数据总线安装驱动
int ret;
ret = ut_i2c_init(0);// 初始化I2C总线0
if(ret == ESP_OK) printf("ut_i2c_init success.\n");
else printf("ut_i2c_init failed.\n");
ret = ut_i2s_init(0); // 初始化I2S总线0
if(ret == ESP_OK) printf("ut_i2s_init success.\n");
else printf("ut_i2s_init failed.\n");
s_i2c_bus = i2c_bus_handle;
i2c_scan_devices(); // 扫描I2C总线上的设备
vTaskDelay(pdMS_TO_TICKS(100));
//初始化与音频编解码器相关的接口,包括数据接口(I2S)、控制接口(I2C)和GPIO接口
audio_codec_i2s_cfg_t i2s_cfg = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
.rx_handle = i2s_keep[0]->rx_handle, // 接收句柄
.tx_handle = i2s_keep[0]->tx_handle, // 发送句柄
#endif
};
const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); // 创建I2S数据接口
audio_codec_i2c_cfg_t i2c_cfg = {.addr = ES7243E_CODEC_DEFAULT_ADDR};// 设置编解码器的默认地址
printf("sign1\n");
#ifdef USE_IDF_I2C_MASTER
i2c_cfg.bus_handle = i2c_bus_handle; // 如果定义了USE_IDF_I2C_MASTER宏,则设置I2C总线句柄i2c_bus_handle touch_bus_handle_BMI
#endif
const audio_codec_ctrl_if_t *in_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
printf("sign2\n");
const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio(); // 创建GPIO接口
es7243e_codec_cfg_t es7243e_cfg = {
.ctrl_if = in_ctrl_if,
};
const audio_codec_if_t *in_codec_if = es7243e_codec_new(&es7243e_cfg);//wenti
printf("sign3\n");
esp_codec_dev_cfg_t dev_cfg = {
.codec_if = in_codec_if,
.data_if = data_if,
.dev_type = ESP_CODEC_DEV_TYPE_IN,
};
esp_codec_dev_handle_t record_dev = esp_codec_dev_new(&dev_cfg);
printf("sign4\n");
ret = esp_codec_dev_set_in_gain(record_dev, 30);
esp_codec_dev_sample_info_t fs = {
.sample_rate = 16000, // 采样率
.channel = 2,
.bits_per_sample = 16, // 每个样本的位数
};
ret = esp_codec_dev_open(record_dev, &fs);
printf("sign5\n");
return record_dev;
}
void ES7243E_Record(esp_codec_dev_handle_t codec_dev, int16_t* audio_data, size_t buffer_size)
{
// printf("开始录音.\n");
esp_codec_dev_read(codec_dev, audio_data, buffer_size); // 读取音频数据(录音)
}
// 获取ES7243E的I2C总线句柄,供BMI270使用
i2c_master_bus_handle_t ES7243E_get_i2c_bus_handle(void)
{
#ifdef USE_IDF_I2C_MASTER
return i2c_bus_handle;
#else
return NULL;
#endif
}
+43
View File
@@ -0,0 +1,43 @@
#ifndef _ES7243E_H_
#define _ES7243E_H_
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "driver/i2c_master.h"
#ifdef __cplusplus
extern "C" {
#endif
#define USE_IDF_I2C_MASTER
/**
* @brief Codec configuration by ESP32S3_s3_N18r8
*/
#define TEST_BOARD_I2C_SDA_PIN GPIO_NUM_50
#define TEST_BOARD_I2C_SCL_PIN GPIO_NUM_51
#define TEST_BOARD_I2S_BCK_PIN GPIO_NUM_28 //SCLK
#define TEST_BOARD_I2S_MCK_PIN GPIO_NUM_29 //MCLK
#define TEST_BOARD_I2S_DATA_WS_PIN GPIO_NUM_27 //LRCK
#define TEST_BOARD_I2S_DATA_IN_PIN GPIO_NUM_26
// #define TEST_BOARD_I2S_DATA_OUT_PIN GPIO_NUM_42
// #define TEST_BOARD_PA_PIN GPIO_NUM_46 //PA_CTRL
#define NST112_I2C_SDA_PIN GPIO_NUM_2
#define NST112_I2C_SCL_PIN GPIO_NUM_3
esp_codec_dev_handle_t ES7243E_Init(void);
void ES7243E_Record(esp_codec_dev_handle_t codec_dev, int16_t* audio_data, size_t buffer_size);
int ut_i2c_init(uint8_t port);
i2c_master_bus_handle_t ES7243E_get_i2c_bus_handle(void);
#ifdef __cplusplus
}
#endif
#endif
+109
View File
@@ -0,0 +1,109 @@
#include <stdio.h>
#include <stdlib.h>
#include "event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
EventGroupHandle_t system_event_group = NULL;
QueueHandle_t system_event_queue = NULL;
bool init_event_system(void)
{
// 检查是否已经初始化
if (system_event_group != NULL)
{
ESP_LOGW("EVENT", "Event system already initialized");
return true;
}
// 创建事件组
system_event_group = xEventGroupCreate();
if (system_event_group == NULL)
{
ESP_LOGE("EVENT", "Failed to create event group");
return false;
}
// 创建事件队列(BLE 回调等通过队列投递,避免在回调中直接操作事件组/任务通知)
system_event_queue = xQueueCreate(8, sizeof(uint32_t));
if (system_event_queue == NULL)
{
ESP_LOGE("EVENT", "Failed to create event queue");
vEventGroupDelete(system_event_group);
system_event_group = NULL;
return false;
}
ESP_LOGI("EVENT", "Event system initialized (group + queue)");
return true;
}
// 清理函数
void deinit_event_system(void)
{
if (system_event_queue != NULL)
{
vQueueDelete(system_event_queue);
system_event_queue = NULL;
}
if (system_event_group != NULL)
{
vEventGroupDelete(system_event_group);
system_event_group = NULL;
}
ESP_LOGI("EVENT", "Event system deinitialized");
}
void set_system_event(uint32_t bits)
{
ESP_LOGI("EVENT", "[DEBUG] set_system_event(0x%lX) group=%p queue=%p main_handle=%p",
(unsigned long)bits, (void *)system_event_group, (void *)system_event_queue, (void *)main_task_handle);
if (system_event_group)
{
xEventGroupSetBits(system_event_group, bits);
}
/* 通过队列投递,main_task 阻塞接收,避免 BLE 回调上下文限制导致事件丢失 */
if (system_event_queue)
{
UBaseType_t q_waiting = uxQueueMessagesWaiting(system_event_queue);
UBaseType_t q_spaces = uxQueueSpacesAvailable(system_event_queue);
ESP_LOGI("EVENT", "[DEBUG] queue before send: pending=%u, spaces=%u",
(unsigned)q_waiting, (unsigned)q_spaces);
BaseType_t ok = xQueueSend(system_event_queue, &bits, 0);
ESP_LOGI("EVENT", "[DEBUG] xQueueSend result=%d (1=ok, 0=full)", (int)ok);
if (!ok)
{
ESP_LOGE("EVENT", "Event queue FULL! bits=0x%lX DROPPED! pending=%u",
(unsigned long)bits, (unsigned)q_waiting);
}
}
else
{
ESP_LOGW("EVENT", "[DEBUG] system_event_queue is NULL!");
}
if (main_task_handle)
{
xTaskNotify(main_task_handle, bits, eSetBits);
}
else
{
ESP_LOGW("EVENT", "[DEBUG] main_task_handle is NULL!");
}
}
void clear_system_event(uint32_t bits)
{
if (system_event_group)
{
xEventGroupClearBits(system_event_group, bits);
}
}
uint32_t wait_system_event(uint32_t bits, TickType_t timeout)
{
if (system_event_group)
{
return xEventGroupWaitBits(system_event_group, bits, pdTRUE, pdFALSE, timeout);
}
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef EVENT_H
#define EVENT_H
#include "freertos/FreeRTOS.h" // 必须第一个包含
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
// 声明全局变量(extern 表示在其他文件中定义)
extern EventGroupHandle_t system_event_group;
extern QueueHandle_t system_event_queue; /* 事件队列:BLE 等回调通过队列投递,main_task 阻塞接收 */
extern TaskHandle_t main_task_handle;
// 事件标志定义
#define BLE_CONNECTED_BIT (1 << 0)
#define WIFI_CONNECTED_BIT (1 << 1)
#define WIFI_DISCONNECTED_BIT (1 << 2)
#define BLE_DISCONNECTED_BIT (1 << 14)
#define WEBSOCKET_CONNECTED_BIT (1 << 3)
#define WEBSOCKET_DISCONNECTED_BIT (1 << 4)
#define WEBSOCKET_DATA_BIT (1 << 5)
#define IMU_DATA_READY_BIT (1 << 6)
#define USER_INPUT_BIT (1 << 7)
#define SYSTEM_ERROR_BIT (1 << 8)
#define OTA_UPDATE_STARTED_BIT (1 << 9)
#define OTA_UPDATE_COMPLETED_BIT (1 << 10)
#define DOWNLOAD_EMOJI_BIT (1 << 11)
#define DOWNLOAD_SUCCESS_BIT (1 << 12)
#define DOWNLOAD_FAILED_BIT (1 << 13)
#define OTA_RECOVER_REQUIRED_BIT (1 << 15)
/** OTA 任务因失败退出:先等 WiFi 重连结果再恢复 UI(由 main 处理) */
#define OTA_FAIL_RECOVER_PENDING_BIT (1 << 16)
/** BLE 触发的 WiFi 配网:重试用尽/最终失败后,释放 WiFi 并恢复 LVGL/UI(仅此场景使用) */
#define WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT (1 << 17)
/** 表情包下载成功后的第一次切换:收尾升级;若切到下载包则强制停在该包 default.json */
#define EMOJI_UPGRADE_FINALIZE_BIT (1 << 18)
/* main_task 监听的系统事件掩码(用于 xEventGroupWaitBits) */
#define MAIN_TASK_EVENT_MASK (BLE_CONNECTED_BIT | WIFI_CONNECTED_BIT | WIFI_DISCONNECTED_BIT | \
WEBSOCKET_CONNECTED_BIT | WEBSOCKET_DISCONNECTED_BIT | WEBSOCKET_DATA_BIT | \
IMU_DATA_READY_BIT | USER_INPUT_BIT | SYSTEM_ERROR_BIT | \
OTA_UPDATE_STARTED_BIT | OTA_UPDATE_COMPLETED_BIT | \
DOWNLOAD_EMOJI_BIT | DOWNLOAD_SUCCESS_BIT | DOWNLOAD_FAILED_BIT | \
BLE_DISCONNECTED_BIT | OTA_RECOVER_REQUIRED_BIT | OTA_FAIL_RECOVER_PENDING_BIT | \
WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT | EMOJI_UPGRADE_FINALIZE_BIT)
// 函数声明
void set_system_event(uint32_t bits);
void clear_system_event(uint32_t bits);
uint32_t wait_system_event(uint32_t bits, TickType_t timeout);
// 新增:初始化和清理函数
bool init_event_system(void);
void deinit_event_system(void);
/* WiFi 配网守卫:收到配网指令后开启故障重启策略 */
void notify_wifi_provision_signal_received(void);
#endif // EVENT_H
+742
View File
@@ -0,0 +1,742 @@
#include "FLASH/flash.h"
#include "esp_http_client.h"
#include "esp_timer.h"
#include "ZIP_EXTRACTOR/zip_extractor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "esp_system.h"
#include "soc/spi_pins.h"
#include "esp_vfs_fat_nand.h"
#include "driver/spi_master.h"
#include "EVENT/event.h"
#include "BLE/ble.h"
#include "EMOJI_STORAGE/emoji_storage.h"
#include "LVGL/lvgl_init.h"
#include <errno.h>
static const char *TAG_FLASH = "example";
/* 字体已由产线直接烧录到外部 Flash,不再从 app 内置资源同步 */
#define UI_EMBED_FONT_BIN_TO_APP 0
/* extflash 访问互斥锁:保护所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 操作 */
static SemaphoreHandle_t s_extflash_mutex = NULL;
/* 解压进行中:动画切换应延后,避免与解压写操作竞争 */
static volatile bool s_extraction_in_progress = false;
void extflash_lock(void)
{
if (s_extflash_mutex) {
xSemaphoreTake(s_extflash_mutex, portMAX_DELAY);
}
}
void extflash_unlock(void)
{
if (s_extflash_mutex) {
xSemaphoreGive(s_extflash_mutex);
}
}
void extflash_extraction_started(void)
{
s_extraction_in_progress = true;
}
void extflash_extraction_finished(void)
{
s_extraction_in_progress = false;
}
bool extflash_is_extracting(void)
{
return s_extraction_in_progress;
}
#define EXTRACT_TASK_STACK_SIZE 16384 // 16KB 栈
#define EXTRACT_TASK_PRIORITY 4
// 分区的挂载路径
const char *base_path = "/extflash";//
// ********** 适配GD5F1GM7UEYIG(SPI NAND)+ WP/HD硬件接3.3V **********
#define EXAMPLE_FLASH_FREQ_KHZ 40000 // 先升到40MHz做稳定性/性能验证(后续可再评估到80MHz)
static spi_nand_flash_device_t *g_nand_flash_handle = NULL;
static spi_device_handle_t g_spi_handle = NULL;
// 磨损均衡句柄(原NOR的WL,NAND驱动内部已实现,保留兼容)
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
void list_files_in_directory(const char *path);
static bool copy_file(const char *source_path, const char *dest_path);
static void extract_task(void *pvParameters);
static StackType_t *s_extract_stack = NULL;
static StaticTask_t *s_extract_tcb = NULL;
static bool example_mount_fatfs(void);
static esp_err_t example_init_nand_flash(void);
static esp_err_t read_chip_id(spi_device_handle_t handle, uint8_t *id_data, size_t len);
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3]);
static bool sync_embedded_fonts_to_extflash(void);
static bool is_zip_file(const char *filename);
static void example_list_data_partitions(void);
static bool sync_embedded_fonts_to_extflash(void)
{
static const char *fonts[] = {
"iconfont_25.bin",
"MiSans_VF_25.bin",
"MiSans_VF_30.bin",
"MiSans_VF_40.bin",
"MiSans_VF_60.bin",
"MiSans_VF_100.bin",
};
bool all_ok = true;
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
#if UI_EMBED_FONT_BIN_TO_APP
ESP_LOGI(TAG_FLASH, "font sync mode: app embed -> extflash");
#else
ESP_LOGI(TAG_FLASH, "font sync mode: extflash preloaded only");
#endif
extflash_lock();
for (size_t i = 0; i < sizeof(fonts) / sizeof(fonts[0]); ++i) {
char dst_path[128] = {0};
struct stat st;
snprintf(dst_path, sizeof(dst_path), "%s/%s", fonts_dir, fonts[i]);
if (stat(dst_path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0) {
ESP_LOGI(TAG_FLASH, "font ready: %s (%ld bytes)", dst_path, (long)st.st_size);
continue;
}
ESP_LOGE(TAG_FLASH, "font missing or invalid: %s", dst_path);
all_ok = false;
}
extflash_unlock();
return all_ok;
}
// 初始化外部Flash芯片
static esp_err_t example_init_nand_flash(void)
{
// SPI总线配置:WP/HD设为-1(硬件接3.3V),适配大传输量
const spi_bus_config_t bus_config = {
.mosi_io_num = PIN_MOSI,
.miso_io_num = PIN_MISO,
.sclk_io_num = PIN_CLK,
.quadhd_io_num = PIN_HD, // -1:禁用HD引脚(硬件接3.3V)
.quadwp_io_num = PIN_WP, // -1:禁用WP引脚(硬件接3.3V)
.max_transfer_sz = 4096 * 2, // 增大传输缓冲区,适配NAND页操作
};
ESP_LOGI(TAG_FLASH, "Initializing GD5F1GM7UEYIG SPI NAND Flash (WP/HD HW 3.3V)");
ESP_LOGI(TAG_FLASH, "Pin assignments:");
ESP_LOGI(TAG_FLASH, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d WP: %2d HD: %2d",
PIN_MOSI, PIN_MISO, PIN_CLK, PIN_CS, PIN_WP, PIN_HD);
ESP_LOGI(TAG_FLASH, "DMA CHANNEL: %d", SPI_DMA_CHAN);
// 初始化SPI总线
ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_config, SPI_DMA_CHAN));
// SPI设备配置:半双工(NAND推荐)、80MHz时钟
spi_device_interface_config_t devcfg = {
.clock_speed_hz = EXAMPLE_FLASH_FREQ_KHZ * 1000,
.mode = 0,
.spics_io_num = PIN_CS,
.queue_size = 10,
.flags = 0,
};
// 添加SPI设备到总线
ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &devcfg, &g_spi_handle));
uint8_t id[4] = {0};
nand_read_jedec_id(g_spi_handle, id);
printf("NAND ID: 0x%02X 0x%02X 0x%02X\n", id[0], id[1], id[2]);
if (id[1] != 0xC8 || id[2] != 0x91) {
ESP_LOGE(TAG_FLASH, "Flash chip ID error! Not GD5F1GM7");
return ESP_ERR_INVALID_VERSION;
}
// SPI NAND Flash专用配置:适配GD5F1GM7的SIO模式(默认),WP/HD硬件禁用
spi_nand_flash_config_t nand_flash_config = {
.device_handle = g_spi_handle,
.io_mode = SPI_NAND_IO_MODE_SIO, // GD5F1GM7支持SIO/DIO/QIO,先选SIO(兼容)
.flags = 0, // 与SPI设备保持一致
};
assert(devcfg.flags == nand_flash_config.flags);
// 初始化NAND Flash设备(驱动自动识别GD5F1GM7芯片ID/容量)
ESP_ERROR_CHECK(spi_nand_flash_init_device(&nand_flash_config, &g_nand_flash_handle));
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash initialized successfully");
return ESP_OK;
}
// ********** 复制JSON文件到NAND Flash(原逻辑不变) **********
static bool copy_json_files_to_ext_flash(const char *source_dir, const char *dest_dir)
{
DIR *dir;
struct dirent *entry;
char source_path[256];
char dest_path[256];
ESP_LOGI(TAG_FLASH, "开始复制JSON文件从 %s 到 %s", source_dir, dest_dir);
dir = opendir(source_dir);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源目录: %s, err: %s", source_dir, strerror(errno));
return false;
}
int file_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *ext = strrchr(entry->d_name, '.');
if (ext != NULL && strcasecmp(ext, ".json") == 0) {
snprintf(source_path, sizeof(source_path), "%s/%s", source_dir, entry->d_name);
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, entry->d_name);
ESP_LOGI(TAG_FLASH, "复制文件: %s -> %s", source_path, dest_path);
if (copy_file(source_path, dest_path)) {
file_count++;
ESP_LOGI(TAG_FLASH, "文件复制成功: %s", entry->d_name);
} else {
ESP_LOGE(TAG_FLASH, "文件复制失败: %s", entry->d_name);
}
}
}
closedir(dir);
ESP_LOGI(TAG_FLASH, "总共复制了 %d 个JSON文件", file_count);
return (file_count > 0);
}
// ********** 复制单个文件(原逻辑不变,NAND写入优化) **********
static bool copy_file(const char *source_path, const char *dest_path)
{
FILE *src_file, *dest_file;
char buffer[4096]; // 增大缓冲区,适配NAND写入
size_t bytes_read;
bool need_lock = (strstr(dest_path, "/extflash") != NULL);
if (need_lock) extflash_lock();
src_file = fopen(source_path, "rb");
if (src_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源文件: %s, err: %s", source_path, strerror(errno));
if (need_lock) extflash_unlock();
return false;
}
dest_file = fopen(dest_path, "wb");
if (dest_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法创建目标文件: %s, err: %s", dest_path, strerror(errno));
fclose(src_file);
if (need_lock) extflash_unlock();
return false;
}
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
ESP_LOGE(TAG_FLASH, "写入文件失败: %s", dest_path);
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return false;
}
fflush(dest_file); // NAND建议及时刷新,避免数据丢失
}
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return true;
}
// ********** 列出目录中的文件(原逻辑不变) **********
void list_files_in_directory(const char *path)
{
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char file_path[256];
dir = opendir(path);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开目录: %s, err: %s", path, strerror(errno));
return;
}
ESP_LOGI(TAG_FLASH, "目录 %s 下的文件/目录:", path);
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name);
if (stat(file_path, &file_stat) == 0) {
if (S_ISREG(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 文件: %s, 大小: %ld 字节", entry->d_name, file_stat.st_size);
} else if (S_ISDIR(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 目录: %s", entry->d_name);
}
}
}
closedir(dir);
}
// ********** 下载文件到NAND Flash(原逻辑微调,适配NAND写入) **********
esp_err_t download_file(char *firmware_url, char *filename)
{
if (filename == NULL || filename[0] == '\0' || !strcmp(filename, ".") || !strcmp(filename, "..")) {
ESP_LOGE(TAG_FLASH, "download_file: 无效文件名");
return ESP_ERR_INVALID_ARG;
}
esp_http_client_config_t config = {
.method = HTTP_METHOD_GET,
.url = firmware_url,
.timeout_ms = 300000,
.buffer_size = 32768,
.buffer_size_tx = 4096,
.keep_alive_enable = true,
.keep_alive_idle = 30,
.keep_alive_interval = 10,
.keep_alive_count = 5,
.disable_auto_redirect = false,
.max_redirection_count = 3,
.auth_type = HTTP_AUTH_TYPE_NONE,
};
typedef struct {
uint32_t total_size;
uint32_t downloaded;
bool download_complete;
esp_err_t last_error;
} download_status_t;
static download_status_t g_download_status = {0};
memset(&g_download_status, 0, sizeof(g_download_status));
esp_err_t ret = ESP_FAIL;
FILE *file = NULL;
uint8_t *buffer = heap_caps_malloc(16384, MALLOC_CAP_SPIRAM);
if (!buffer) {
ESP_LOGE(TAG_FLASH, "Failed to allocate buffer");
return ESP_ERR_NO_MEM;
}
char full_path[256];
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, filename);
extflash_lock();
struct stat st;
if (stat(full_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
ESP_LOGI(TAG_FLASH, "Removing existing file: %s", full_path);
remove(full_path);
}
mkdir(base_path, 0755);
/* 文件名可带子目录,例如 eaf/foo.eaf → /extflash/eaf/ */
{
char parent[256];
snprintf(parent, sizeof(parent), "%s", full_path);
char *slash = strrchr(parent, '/');
if (slash && slash != parent) {
*slash = '\0';
if (strcmp(parent, base_path) != 0 && mkdir(parent, 0755) < 0 && errno != EEXIST) {
ESP_LOGW(TAG_FLASH, "mkdir %s 失败: %s", parent, strerror(errno));
}
}
}
// 断点重连循环(减少重试次数,NAND不建议频繁重试)
for (int retry_count = 0; retry_count <= 5; retry_count++) {
if (retry_count > 0) {
extflash_unlock();
ESP_LOGW(TAG_FLASH, "Retry attempt %d/5", retry_count);
vTaskDelay(pdMS_TO_TICKS(2000 * retry_count));
extflash_lock();
}
file = fopen(full_path, "ab+");
if (!file) {
ESP_LOGE(TAG_FLASH, "Failed to open file: %s, err: %s", full_path, strerror(errno));
continue;
}
fseek(file, 0, SEEK_END);
size_t current_size = ftell(file);
ESP_LOGI(TAG_FLASH, "Current file size: %zu bytes", current_size);
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG_FLASH, "Failed to initialize HTTP client");
fclose(file);
extflash_unlock();
continue;
}
esp_http_client_set_header(client, "Content-Type", "text/plain");
if (current_size > 0) {
char range_header[64];
snprintf(range_header, sizeof(range_header), "bytes=%zu-", current_size);
esp_http_client_set_header(client, "Range", range_header);
ESP_LOGI(TAG_FLASH, "Setting Range header: %s", range_header);
}
ret = esp_http_client_open(client, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG_FLASH, "Failed to open HTTP connection: %s", esp_err_to_name(ret));
esp_http_client_cleanup(client);
fclose(file);
extflash_unlock();
continue;
}
int content_length = esp_http_client_fetch_headers(client);
g_download_status.total_size = (content_length > 0) ? (content_length + current_size) : 0;
if (content_length > 0) {
ESP_LOGI(TAG_FLASH, "Content length: %d bytes, Total expected: %" PRIu32 " bytes",
content_length, (uint32_t)g_download_status.total_size);
}
g_download_status.downloaded = current_size;
// 下载数据
int read_len;
bool download_error = false;
int64_t last_progress_time = esp_timer_get_time();
uint32_t last_progress_size = current_size;
while (true) {
read_len = esp_http_client_read(client, (char *)buffer, 16384);
if (read_len < 0) {
ESP_LOGE(TAG_FLASH, "Error reading HTTP data");
download_error = true;
break;
} else if (read_len == 0) {
ESP_LOGI(TAG_FLASH, "HTTP read complete");
break;
}
if (fwrite(buffer, 1, read_len, file) != read_len) {
ESP_LOGE(TAG_FLASH, "Write error: expected %d, got %zu", read_len, (size_t)fwrite(buffer,1,read_len,file));
download_error = true;
break;
}
if (g_download_status.downloaded % 65536 == 0) {
fflush(file);
}
g_download_status.downloaded += read_len;
// 打印下载进度
int64_t current_time = esp_timer_get_time();
if (current_time - last_progress_time > 1000000) {
uint32_t speed = (g_download_status.downloaded - last_progress_size) / 1024;
if (g_download_status.total_size > 0) {
int percent = (g_download_status.downloaded * 100) / g_download_status.total_size;
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 "/%" PRIu32 " bytes (%d%%) - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size, percent, (uint32_t)speed);
} else {
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 " bytes - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)speed);
}
last_progress_time = current_time;
last_progress_size = g_download_status.downloaded;
}
if (esp_http_client_get_status_code(client) == 408) {
ESP_LOGE(TAG_FLASH, "HTTP timeout occurred");
download_error = true;
break;
}
}
fflush(file);
fclose(file);
if (download_error) {
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
if (g_download_status.total_size > 0 && g_download_status.downloaded < g_download_status.total_size) {
ESP_LOGW(TAG_FLASH, "Download incomplete: %" PRIu32 "/%" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size);
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
// 下载成功,创建解压任务
ESP_LOGI(TAG_FLASH, "Download completed successfully: %" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded);
esp_http_client_cleanup(client);
if (!is_zip_file(filename)) {
ESP_LOGI(TAG_FLASH, "非 ZIP,已写入 %s(%" PRIu32 " 字节),跳过解压",
full_path, (uint32_t)g_download_status.downloaded);
extflash_unlock();
free(buffer);
return ESP_OK;
}
char *filename_copy = strdup(filename);
if (!filename_copy) {
ESP_LOGE(TAG_FLASH, "strdup(filename) failed");
extflash_unlock();
free(buffer);
return ESP_ERR_NO_MEM;
}
size_t stack_size = 256 * 1024;
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
s_extract_tcb = pvPortMalloc(sizeof(StaticTask_t));
if (s_extract_stack && s_extract_tcb) {
ESP_LOGI(TAG_FLASH, "[DEBUG] Creating extract_task (static, SPIRAM stack %u bytes)", (unsigned)stack_size);
xTaskCreateStatic(extract_task, "extract_task", stack_size / sizeof(StackType_t),
(void *)filename_copy, configMAX_PRIORITIES - 1,
s_extract_stack, s_extract_tcb);
} else {
ESP_LOGW(TAG_FLASH, "[DEBUG] SPIRAM alloc failed, falling back to dynamic task");
xTaskCreatePinnedToCore(extract_task, "extract_task", stack_size, (void *)filename_copy,
configMAX_PRIORITIES - 1, NULL, 1);
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = NULL;
s_extract_tcb = NULL;
}
extflash_unlock();
free(buffer);
return ESP_OK;
}
extflash_unlock();
free(buffer);
ESP_LOGE(TAG_FLASH, "Download failed after all retries");
return ESP_FAIL;
}
static void extract_task(void *pvParameters)
{
char *filename_copy = (char *)pvParameters;
ESP_LOGI("EXTRACT", "[DEBUG] extract_task started, file=%s, core=%d, prio=%d, stack_hwm=%u",
filename_copy ? filename_copy : "NULL",
(int)xPortGetCoreID(),
(int)uxTaskPriorityGet(NULL),
(unsigned)uxTaskGetStackHighWaterMark(NULL));
extflash_extraction_started();
extflash_lock();
if (filename_copy) {
extract_json_zip(filename_copy);
free(filename_copy);
}
list_files_in_directory(base_path);
/* ── Fallback: main_task 不可用时直接处理下载结果 ── */
if (main_task_handle == NULL)
{
char pack_id = zip_extractor_get_last_downloaded_pack();
if (pack_id != '0')
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_SUCCESS directly (pack='%c')", pack_id);
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_SUCCESS, eSetBits);
emoji_storage_add_downloaded_pack(pack_id);
emoji_storage_save_using_pack(pack_id);
ESP_LOGI("EXTRACT", "pack '%c' saved as downloaded & using", pack_id);
char path_buf[64];
snprintf(path_buf, sizeof(path_buf), "/extflash/%c/default.json", pack_id);
struct stat st;
if (stat(path_buf, &st) == 0)
{
ESP_LOGI("EXTRACT", "Playing animation: %s (%ld bytes)", path_buf, (long)st.st_size);
request_play_lottie_path(path_buf);
}
else
{
ESP_LOGW("EXTRACT", "Animation file not found: %s, skip play", path_buf);
}
g_emoji_upgrade_downloaded_pack = pack_id;
g_emoji_upgrade_pending_finalize = true;
ESP_LOGI("EXTRACT", "下载成功 pack='%c':等待首次切换后收尾(不重启,不回原来)", pack_id);
clear_system_event(DOWNLOAD_SUCCESS_BIT);
}
else
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_FAILED directly");
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_FAILED, eSetBits);
clear_system_event(DOWNLOAD_FAILED_BIT);
}
}
extflash_unlock();
extflash_extraction_finished();
/*
ESP_LOGI("EXTRACT", "[DEBUG] extract_task finished, about to delete self. "
"queue_pending=%d, stack_hwm=%u",
(int)uxQueueMessagesWaiting(system_event_queue),
(unsigned)uxTaskGetStackHighWaterMark(NULL)); //这里解压时注意可能死机
*/
vTaskDelete(NULL);
}
// ********** 检查文件是否为ZIP格式(原逻辑不变) **********
static bool is_zip_file(const char *filename)
{
if (!filename) return false;
const char *dot = strrchr(filename, '.');
return (dot && strcasecmp(dot, ".zip") == 0);
}
// ********** 反初始化NAND Flash(新增,用于程序退出/复位) **********
static void example_deinit_nand_flash(void)
{
if (g_nand_flash_handle) {
esp_vfs_fat_nand_unmount(base_path, g_nand_flash_handle);
spi_nand_flash_deinit_device(g_nand_flash_handle);
g_nand_flash_handle = NULL;
}
if (g_spi_handle) {
spi_bus_remove_device(g_spi_handle);
g_spi_handle = NULL;
}
spi_bus_free(HOST_ID);
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash deinitialized");
}
// ********** 正确 NAND 版本:无参数 **********
static bool example_mount_fatfs(void)
{
ESP_LOGI(TAG_FLASH, "挂载 NAND FAT 文件系统");
if (g_nand_flash_handle == NULL) {
ESP_LOGE(TAG_FLASH, "NAND Flash 句柄为空");
return false;
}
// FAT 配置(NAND 专用)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 6,
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif
.allocation_unit_size = 16 * 1024,
};
// NAND 专用挂载接口(不是 NOR 的 spiflash_mount!)
esp_err_t err = esp_vfs_fat_nand_mount(base_path, g_nand_flash_handle, &mount_config);
if (err != ESP_OK) {
ESP_LOGE(TAG_FLASH, "NAND FATFS 挂载失败: %s (0x%x)", esp_err_to_name(err), err);
return false;
}
// 打印容量
uint64_t bytes_total, bytes_free;
esp_vfs_fat_info(base_path, &bytes_total, &bytes_free);
ESP_LOGI(TAG_FLASH, "NAND FAT 挂载成功: %s", base_path);
ESP_LOGI(TAG_FLASH, "总容量: %" PRIu64 " KB, 剩余: %" PRIu64 " KB",
bytes_total / 1024, bytes_free / 1024);
return true;
}
// GD5F1GM7UEYIG 读 ID 函数
// 输出:id[0] = 0xC8, id[1] = 0x91, id[2] = 0x01
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3])
{
if (handle == NULL || id == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(id, 0, 3);
// GD5F1GM7 标准 Read ID 时序:必须读 4 字节
uint8_t tx_data[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx_data[4] = {0};
spi_transaction_t t = {
.length = 4 * 8, // 必须 32bit,才能读到 0x91
.tx_buffer = tx_data,
.rx_buffer = rx_data,
};
esp_err_t ret = spi_device_transmit(handle, &t);
if (ret == ESP_OK) {
id[0] = rx_data[1]; // 0xC8 厂商ID
id[1] = rx_data[2]; // 0x91 设备ID ✅
id[2] = rx_data[3]; // 扩展ID
}
return ret;
}
static void example_list_data_partitions(void)
{
ESP_LOGI(TAG_FLASH, "Listing data partitions:");
// 查找所有数据类型的分区
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL);
// 遍历并打印每个分区的信息
for (; it != NULL; it = esp_partition_next(it))
{
const esp_partition_t *part = esp_partition_get(it);
ESP_LOGI(TAG_FLASH, "- partition '%s', subtype %d, offset 0x%" PRIx32 ", size %" PRIu32 " kB",
part->label, part->subtype, part->address, part->size / 1024);
}
esp_partition_iterator_release(it); // 释放迭代器
}
// ********** 主入口函数(替换原flash_main,适配NAND+WP/HD接3.3V) **********
void flash_main(void)
{
if (s_extflash_mutex == NULL) {
s_extflash_mutex = xSemaphoreCreateMutex();
if (s_extflash_mutex == NULL) {
ESP_LOGE(TAG_FLASH, "Failed to create extflash mutex");
}
}
// 1. 初始化GD5F1GM7UEYIG SPI NAND Flash(WP/HD硬件接3.3V)
if (example_init_nand_flash() != ESP_OK) {
ESP_LOGE(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化失败");
return;
}
// 2. 挂载NAND FAT文件系统
if (!example_mount_fatfs()) {
ESP_LOGE(TAG_FLASH, "NAND FAT文件系统挂载失败,无法继续");
example_deinit_nand_flash();
return;
}
example_list_data_partitions();
// 3. 校验 /extflash/fonts 下字体是否就绪(不再从 app 内同步)
if (!sync_embedded_fonts_to_extflash()) {
ESP_LOGW(TAG_FLASH, "部分字体缺失/异常,运行时可能回退到默认字体");
}
// 4. 列出NAND Flash中的文件(含 fonts 目录,便于产线验收字体)
extflash_lock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash中的文件列表:");
list_files_in_directory(base_path);
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
ESP_LOGI(TAG_FLASH, "字体目录文件列表: %s", fonts_dir);
list_files_in_directory(fonts_dir);
extflash_unlock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化&挂载完成,业务逻辑就绪");
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef FLASH_H
#define FLASH_H
#pragma GCC diagnostic ignored "-Wformat-truncation"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "esp_flash.h"
#include "esp_flash_spi_init.h"
#include "esp_partition.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "esp_system.h"
#include "soc/spi_pins.h"
#include <dirent.h>
#include <sys/stat.h>
#define HOST_ID SPI3_HOST // 使用SPI3主机(VSPI)
#define PIN_MOSI 39
#define PIN_MISO 41
#define PIN_CLK 40
#define PIN_CS 42
#define PIN_WP -1
#define PIN_HD -1
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO // DMA通道:自动选择
/* extflash 访问互斥:所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 等操作需在持锁期间进行 */
void extflash_lock(void);
void extflash_unlock(void);
/* 解压进行中标志:动画切换应延后,避免与解压写操作竞争 */
void extflash_extraction_started(void);
void extflash_extraction_finished(void);
bool extflash_is_extracting(void);
void list_files_in_directory(const char *path);
static bool copy_file(const char *source_path, const char *dest_path);
static void extract_task(void *pvParameters);
static bool example_mount_fatfs(void);
void flash_main(void);
esp_err_t download_file(char *firmware_url, char *filename);
static esp_err_t read_chip_id(spi_device_handle_t handle, uint8_t *id_data, size_t len);
#endif // FLASH_H
+35
View File
@@ -0,0 +1,35 @@
#include "cJSON.h"
#include "json.h"
// KEY_VALUE_PAIR data_pair =
// {
// .key = "action",
// .value = NULL,
// .son1_pair =
// }
void json_create(const char *str)
{
cJSON *report_msg = cJSON_CreateObject();
cJSON_AddStringToObject(report_msg, "action", "message");
cJSON_AddStringToObject(report_msg, "target_id", "0");
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(data, "status", "download_finish_ok");
cJSON_AddItemToObject(report_msg, "data", data);
char *report_str = cJSON_PrintUnformatted(report_msg);
}
cJSON* json_create_any_type(int type,cJSON *root, const char *key, const char *value)
{
cJSON *root = cJSON_CreateObject();
switch(type)
{
case 1://string
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef __JSON_H__
#define __JSON_H__
typedef struct
{
char* key;
char* value;
KEY_VALUE_PAIR son1_pair;
KEY_VALUE_PAIR son2_pair;
KEY_VALUE_PAIR son3_pair;
KEY_VALUE_PAIR son4_pair;
KEY_VALUE_PAIR son5_pair;
}KEY_VALUE_PAIR;
#endif
+20
View File
@@ -0,0 +1,20 @@
menu "Example Configuration"
config BROKER_URL
string "Broker URL"
default "mqtt://mqtt.eclipseprojects.io"
help
URL of the broker to connect to
config BROKER_URL_FROM_STDIN
bool
default y if BROKER_URL = "FROM_STDIN"
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
bool "Format the flash if mount failed"
default n
help
If this config item is set, format_if_mount_failed will be set to true and the
NAND FAT filesystem will be formatted if the mount has failed.
endmenu
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
#ifndef __LVGL_INIT_H__
#define __LVGL_INIT_H__
#include "config.h"
#define extraflash_flag (1 << 0)
#define NOTIFY_EXIT_ALL_MODES 0xFFFE
/* 下载成功后按路径播放:request_play_lottie_path 写入路径,change_task 收到 0xFFFF 时从此缓冲区取路径播放 */
#define NOTIFY_PLAY_BY_PATH 0xFFFF
/* 动画完成回调中不能持锁切换,由 change_task 延迟执行 */
#define NOTIFY_RESUME_MUSIC_LOOP 0xFFFC
#define NOTIFY_MUSIC_INTRO_DONE 0xFFFB
#define NOTIFY_SENTINEL_DO_SWITCH 0xFFFA
#define NOTIFY_SWITCH_BY_QUEUE 0xFFF9
#define NOTIFY_MUSIC_LISTEN_ANOTHER_PASS 0xFFF8
#define NOTIFY_MUSIC_REPLAY_INTRO 0xFFF7
#define NOTIFY_ACTION_QUEUE_READY 0xFFF6
#define NOTIFY_RESUME_CALLING_LOOP 0xFFF5
/* 通话中插播表情时收到挂断:等插播播完后由 completed_cb 通知,再回默认 */
#define NOTIFY_DEFERRED_HANGUP 0xFFF4
#define NOTIFY_START_AI_RESPOND_LOOP 0xFFF3
#define NOTIFY_RESUME_AI_RESPOND_LOOP 0xFFF2
#define LOTTIE_BUF_SIZE (LOTTIE_ANIM_WIDTH * LOTTIE_ANIM_HEIGHT * 4)
extern char *lottie_file_path;
extern int lottie_id;
extern int last_lottie_id;
/* 系统主初始化流程完成标志:true 后才允许处理 BLE 业务指令 */
extern volatile bool g_system_init_done;
/** BLE play_sticker 正在展示表情时置 true,回到默认等场景清 false;IMU 幸运表情需检查此标志 */
extern volatile bool g_ble_play_sticker_active;
/* BLE play_sticker 单槽覆盖队列:播放中收到新指令时,仅保留最后一条,待当前动画播完后切换 */
extern volatile bool g_ble_play_sticker_pending_valid;
extern volatile uint16_t g_ble_play_sticker_pending_action_id;
extern volatile uint8_t g_ble_play_sticker_pending_direction;
/* KTV 持续循环播放态标志(用于 BLE 直通插播判断) */
extern volatile bool g_is_ktv_playing;
extern bool back2default;
extern bool first_display;
extern StackType_t *main_task_stack;
extern StaticTask_t main_task_tcb;
extern _lock_t lvgl_api_lock;
extern esp_lcd_panel_handle_t panel_handle;
extern esp_lcd_panel_io_handle_t io_handle;
extern const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[9];
extern volatile char g_target_path[32];
extern TaskHandle_t change_task_handle;
/** 当前任务是否为 LVGL port 任务(该任务上调用 LVGL API 时通常已持有 lvgl_api_lock) */
bool lvgl_port_task_is_current(void);
extern esp_lcd_panel_handle_t panel_handle;
extern const char *lottie_files[];
extern int64_t last_activity_timestamp;
extern bool block_auto_sleep;
/** 表情包下载流程进行中:收到新的下载指令时应忽略(避免并发) */
extern volatile bool g_emoji_download_in_progress;
/** 刷新自动休眠空闲时间戳(触摸、BLE 指令等) */
void system_keep_alive(void);
/** 设置是否阻止自动休眠(OTA / 配网等场景使用) */
void set_block_auto_sleep(bool block);
typedef struct
{
wakenet_state_t wakenet_mode;
esp_mn_state_t state;
int command_id;
} sr_result_t;
void set_lottie_id(int id);
void lottie_init(void);
esp_err_t app_sr_init(void);
esp_err_t app_lcd_init(void);
esp_err_t app_lvgl_init(void);
esp_err_t app_change_init(void);
esp_err_t BMI_init(void);
void switch_lottie_animation(int anim_index);
void load_brightness_from_nvs(void);
/** 将设置页亮度滑条与 get_system_brightness_percent() 对齐;可从任意任务调用,内部经 lv_async_call 投递到 LVGL 线程 */
void request_brightness_slider_sync(void);
esp_err_t app_touch_init(void);
void switch_lottie_animation_cross_path(const char *emoji_path);
void random_emoji(uint16_t n);
void request_play_lottie_path(const char *path);
void set_current_lottie_range_ratio(float start_ratio, float end_ratio);
void lvgl_deinit_for_wifi(void);
/* 向 change_task 投递 BLE 动作指令(队列化,避免与音乐内部通知互相覆盖) */
bool change_task_post_action(uint16_t action_id);
/** 获取当前正在展示的 Lottie 路径(升级快照等场景使用) */
const char *lvgl_get_current_lottie_path(void);
/** 清空当前路径缓存(用于强制重播同一路径) */
void lvgl_clear_current_lottie_path(void);
void lottie_anim_completed_cb(lv_anim_t *anim);
bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#endif
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
#ifndef __LVGL_LOTTIE_H__
#define __LVGL_LOTTIE_H__
#include "config.h"
extern lv_obj_t *ui_Screen_Lottie;
extern lv_obj_t *g_music_preload_lottie;
extern uint8_t *g_music_preload_fb;
extern char g_music_preload_path[];
extern bool g_music_preload_ready;
extern bool resume_music_loop_after_oneshot; /* 持续播放音乐时收到其他指令,播完该指令一次后切回音乐段 */
extern bool resume_calling_loop_after_oneshot; /* 通话循环段被插播时,插播播完后恢复 20%-80% 循环 */
void handle_normal_sticker(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
bool handle_play_music(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
bool handle_sentinel_mode(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
void reset_all_playback_state(void);
void switch_to_default_emoji(void);
bool handle_stop_music(uint16_t action_id, int *last_active_page_ptr);
bool handle_internal_commands(uint32_t bits, int *last_active_page_ptr);
void clear_music_preload(void);
void try_preload_music_listen_lottie(const char *path);
void set_current_lottie_range_time(float start_s, float end_s);
/** 通话接听后处于循环段时为 true;与 is_playing_music 类似,供 BLE 直通打断 pending */
bool lvgl_lottie_calling_active_for_ble(void);
/** AI 回复循环态(conversation.json)时为 true,供 BLE 持续态直通判断 */
bool lvgl_lottie_ai_respond_loop_active_for_ble(void);
/** play_sticker 是否跳过电机:仅 AI 循环内 0x0045、及 direction==0+resume/循环态(误回中);同向重复由 motor 层处理 */
bool lvgl_lottie_should_skip_play_sticker_motor(uint16_t action_id, uint8_t direction);
/** 正念 breath 两遍播放窗口内返回 true;BLE 侧据此丢弃所有指令 */
bool lvgl_lottie_mindfulness_block_ble_for_ble(void);
/** lottie_anim_completed_cb 内优先调用:若存在「插播中挂起的挂断」,则通知 change_task 回默认并返回 true */
bool lvgl_lottie_completed_try_deferred_hangup(void);
/** lottie_anim_completed_cb 内调用:若 AI 回复循环被插播,通知恢复并返回 true */
bool lvgl_lottie_completed_try_resume_ai_respond_loop(void);
/** lottie_anim_completed_cb 内调用:若 0x0020/21/2E/2F hearing 段已播完,通知启动 AI 回复循环并返回 true */
bool lvgl_lottie_completed_try_start_ai_respond_loop(void);
#endif
+6
View File
@@ -0,0 +1,6 @@
#include "lvgl_lottie_logic.h"
+6
View File
@@ -0,0 +1,6 @@
#ifndef __LVGL_LOTTIE_LOGIC_H__
#define __LVGL_LOTTIE_LOGIC_H__
#include "lvgl_init.h"
#include "config.h"
#endif
+82
View File
@@ -0,0 +1,82 @@
#include "LVGL/lvgl_src.h"
const char* lottie_files[] =
{
"/extflash/0/default.json", // 默认表情1 √
"/extflash/0/calling.json", // 拨打电话2 √
"/extflash/0/camping.json", // 露营模式3 √
"/extflash/0/charging.json", // 充电中4 √
"/extflash/0/charging-complete.json", // 充电完成5 √
"/extflash/0/cold.json", // 打开冷气6 √
"/extflash/0/guard.json", // 戴上墨镜?7 √
"/extflash/0/hearing.json", // 倾听中8 √
"/extflash/0/hello.json", // 打招呼9 √
"/extflash/0/hot.json", // 打开暖气10 √
"/extflash/0/music.json", // 打开音乐11 √
"/extflash/0/navigation.json", // 导航模式12 √
"/extflash/0/protect.json", // 哨兵模式13 √
"/extflash/0/saftybelt-no.json", // 安全带未系好14 √
"/extflash/0/saftybelt.json", // 安全带已系好15 √
"/extflash/0/snap.json", // 打瞌睡16 √
"/extflash/0/start.json", // 启动17 √
"/extflash/0/turnaround.json", // 左看右看18 √
"/extflash/0/washing.json", // 清洗中19 √
"/extflash/0/welcome.json", // 欢迎20 √
"/extflash/0/window-left.json", // 打开左窗21 √
"/extflash/0/window-right.json", // 打开右窗22 √
"/extflash/0/arrive.json", // 导航到达23 √
"/extflash/0/bless.json", // 抽签24 √
"/extflash/0/break.json", // 急刹车25 √
"/extflash/0/breath.json", // 冥想26 √
"/extflash/0/horoscope.json", // 星座运势27 √
"/extflash/0/money.json", // 颠簸28 √
"/extflash/0/sing.json", // 唱歌29 √
"/extflash/0/speedup.json", // 急加速30 √
"/extflash/0/turnright.json", // 右转弯31 √
"/extflash/0/turnleft.json", // 左转弯32 √
"/extflash/0/helloleft.json", // 打招呼左33 √
"/extflash/0/music-listen.json", // 持续播放音乐34 √
//待机随机表情包
"/extflash/0/tired.json", // 疲劳驾驶tired35 --- 新增12个表情;
"/extflash/0/trafficjam.json", // 堵车 36
"/extflash/0/holiday.json", //假期holiday 37 √
"/extflash/0/calmdown.json", //冷静 38 √
"/extflash/0/becool.json", // 装酷becool 39 --- 新增12个表情; √
"/extflash/0/boring.json", // 无聊boring 40 √
"/extflash/0/cheer.json", // 庆祝cheer 41 √
"/extflash/0/cry.json", // 哭cry 42 √
"/extflash/0/curious.json", // 好奇curious 43 √
"/extflash/0/eating.json", // 吃eating 44 √
"/extflash/0/happy.json", // 高兴happy 45 √
"/extflash/0/like.json", // 喜欢like 46 √
"/extflash/0/liuliuliu.json", // 666🤙🤙🤙 47 √
"/extflash/0/love.json", // 爱心 love 48 √
"/extflash/0/shy.json", // 害羞 shy 49 √
"/extflash/0/thinking.json", // 思考thinking 🤔 50 √
"/extflash/0/autodrive.json", //智能驾驶模式 51 √
"/extflash/0/backhome.json", //回家 52 √
"/extflash/0/outhome.json", //离家 53 √
"/extflash/0/card.json", //智付 刷卡 54 √
"/extflash/0/toofast.json", //超速120 55 √
"/extflash/0/2hours.json", //连续驾驶2小时 56 √
"/extflash/0/trafficlight.json", //交通灯 57 √
"/extflash/0/conversation.json", //AI 语音回答 58 √
"/extflash/0/parktronic.json", //自动泊车 59 √
"/extflash/0/backcar.json", //倒车 60 √
"/extflash/0/distract.json", //分心担忧紧张 61 √
"/extflash/0/deng.json", //大灯 62 √
"/extflash/0/windshield-wiper.json", //雨刮 63 √
"/extflash/0/birthday.json", //生日庆祝 64 √
"/extflash/0/child-lock.json", //儿童锁--上锁 65 √
"/extflash/0/child-unlock.json", //儿童锁--解锁 66 √
"/extflash/0/conversation1.json", //语音回复随机表情1 √
"/extflash/0/conversation2.json", //语音回复随机表情2 √
};
const uint32_t lottie_files_length = sizeof(lottie_files)/sizeof(lottie_files[0]);
+86
View File
@@ -0,0 +1,86 @@
#ifndef __LVGL_SRC_H__
#define __LVGL_SRC_H__
#include <stdint.h>
typedef enum
{
default_emojo = 0, //默认表情 默认表情 1
calling_emojo, //打电话 打电话2
camping_emojo, //露营模式 露营模式3
charging_emojo, //开始充电 开始充电4
charged_over_emojo, //充电完成 充电完成5
cold_air_emojo, //冷风 冷风6
guard_emojo, //墨镜😎哨兵模式 哨兵模式7
hearing_emojo, //聆听 聆听8
hello_emojo, //打招呼 打招呼9
warm_air_emojo, //暖风 暖风10
music_emojo, //播放音乐 播放音乐11
navigation_modle_emojo, //开始导航 开始导航12
protect_emojo, //保护--哨兵模式 左看右看表情13//保护
saftybelt_no_emojo, //未系好安全带 未系好安全带14
saftybelt_emojo, //系安全带 系安全带15
snap_emojo, //小憩模式 小憩模式16
start_emojo, //驾车出发 驾车出发17
turn_around_emojo, //左看右看表情 左看右18
washing_emojo, //洗车模式 洗车模式19
welcome_emojo, //迎宾模式 迎宾模式20
open_left_window_emojo, //主驾开窗 主驾开窗21
open_right_window_emojo, //副驾开窗 副驾开窗22
arrive_emojo, //到达目的地 到达目的地23
bless_emojo, //祈祷🙏 祈祷24
break_emojo, //刹车 刹车25
breath_emojo, //冥想 冥想26
horoscope_emojo, //星座运势 星座运势27
money_emojo, //金币 掉金币28
sing_emojo, //唱歌 唱歌29
speedup_emojo, //超速 超速30
turn_right_emojo, //右转向 右转向31
turn_left_emojo, //左转向 左转向32
hello_left_emojo, //打招呼左 打招呼左33
music_listen_emojo, //持续播放音乐 持续播放音乐34
tried_emojo, //疲劳驾驶 喝咖啡35
trafficjam_emojo, //堵车 🐌 堵车36
holiday_emojo, //假期 假期 庆祝🎉37
calm_down_emojo, //冷静 冷静38
becool_emojo, //装酷 39
boring_emojo, //无聊 40
cheer_emojo, //庆祝 41
cry_emojo, //哭 42
curious_emojo, //好奇 43
eating_emojo, //吃 44
happy_emojo, //高兴 45
like_emojo, //喜欢 46
liuliuliu_emojo, //666 47
love_emojo, //爱心 48
shy_emojo, //害羞 49
thinking_emojo, //思考 50
autodrive_emojo, //智能驾驶模式 智能驾驶模式51
backhome_emojo, //回家 52
outhome_emojo, //离家 53
card_emojo, //智付 刷卡 54
toofast_emojo, //超速120 55
_2h_emojo, //连续驾驶2小时 56
traffic_light_emojo, //红绿灯 57
AI_respond_emojo, //AI 回复 58
auto_parking_emojo, //自动泊车 59
back_car_emojo, //倒车 60
distract_emojo, //分心担忧紧张 61
deng_emojo, //大灯 62
windshield_wiper_emojo, //雨刮 63
birthday_emojo, //生日庆祝 64
child_lock_emojo, //儿童锁--上锁 65
child_unlock_emojo, //儿童锁--解锁 66
conversation1_emojo, //语音回复随机表情1 67
conversation2_emojo, //语音回复随机表情2 68
}LOTTIE_FILES;
extern const char* lottie_files[];
extern const uint32_t lottie_files_length;
#endif
+7674
View File
File diff suppressed because it is too large Load Diff
+1338
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
## Miniz
Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo.
## Usage
Releases are available at the [releases page](https://github.com/richgel999/miniz/releases) as a pair of `miniz.c`/`miniz.h` files which can be simply added to a project. To create this file pair the different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) during build. Alternatively use as cmake or meson module (or build system of your choice).
## Features
* MIT licensed
* A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio.
* Easily tuned and trimmed down by defines
* A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip).
* Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings.
* Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got.
* Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way.
* Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c
* A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.)
## Building miniz - Using vcpkg
You can download and install miniz using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install miniz
The miniz port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
## Known Problems
* No support for encrypted archives. Not sure how useful this stuff is in practice.
* Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features.
## Special Thanks
Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long).
Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples.
## Patents
I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](https://datatracker.ietf.org/doc/html/rfc1951#section-4). Also see the [gzip FAQ](https://web.archive.org/web/20160308045258/http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too.
+195
View File
@@ -0,0 +1,195 @@
#include <math.h>
#include "MOTOR/motor_control.h"
#include "foc.h"
#include "esp_check.h"
#include "motor_timer.h"
#include "step_motor_init.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "motor_control.h"
volatile short Motor_Speed = 0;
volatile short A_PULS_DUTY = 0;
volatile short A_MIUNS_DUTY = 0;
volatile short B_PULS_DUTY = 0;
volatile short B_MIUNS_DUTY = 0;
volatile float Ud = 0;
volatile float Uq = 0;
volatile float theta = 0;
volatile float U_ALPHA = 0;
volatile float U_BETA = 0;
#if MOTOR_WITH_MAGNET_RING
static const float speed_adj = 0.1f; //0.22
static const float voltage_adj = 0.22f; //0.32
#else
static const float speed_adj = 0.13f; //0.22
static const float voltage_adj = 0.22f; //0.32
#endif
static TaskHandle_t s_foc_task_handle = NULL;
static const char *TAG_FOC_CTRL = "foc_ctrl";
static int64_t s_last_duty_log_us = 0;
static uint16_t clamp_duty(short duty)
{
if (duty < 0) {
return 0;
}
if (duty > (short)STEPPER_DUTY_MAX) {
return STEPPER_DUTY_MAX;
}
return (uint16_t)duty;
}
static void foc_update_output_once(void)
{
float speed;
Uq = voltage_adj;
Ud = 0;
if(motor_dir == MOTOR_CW)
{
speed = speed_adj;
}
if(motor_dir == MOTOR_CCW)
{
speed = -speed_adj;
}
theta += speed;
if (theta >= (2.0f * (float)M_PI)) {
theta -= (2.0f * (float)M_PI);
}
inv_park(Ud, Uq, theta, &U_ALPHA, &U_BETA);
PWM_DRIVER_OPENLOOP(U_ALPHA, U_BETA);
stepper_motor_set_duty_raw(clamp_duty(A_PULS_DUTY),
clamp_duty(A_MIUNS_DUTY),
clamp_duty(B_PULS_DUTY),
clamp_duty(B_MIUNS_DUTY));
// int64_t now_us = esp_timer_get_time();
// if (now_us - s_last_duty_log_us >= 1000000) {
// s_last_duty_log_us = now_us;
// // ESP_LOGI(TAG_FOC_CTRL,
// // "duty A+:%d A-:%d B+:%d B-:%d theta:%.3f U_ALPHA:%.3f U_BETA:%.3f",
// // A_PULS_DUTY,
// // A_MIUNS_DUTY,
// // B_PULS_DUTY,
// // B_MIUNS_DUTY,
// // (double)theta,
// // (double)U_ALPHA,
// // (double)U_BETA);
// // }
}
static void foc_task(void *arg)
{
(void)arg;
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
foc_update_output_once();
}
}
void park(float ia, float ib, float theta, float *id, float *iq)
{
float cos_theta = cosf(theta);
float sin_theta = sinf(theta);
*id = ia * cos_theta + ib * sin_theta;
*iq = -ia * sin_theta + ib * cos_theta;
}
void inv_park(float vd, float vq, float theta, float *valpha, float *vbeta)
{
float cos_theta = cosf(theta);
float sin_theta = sinf(theta);
*valpha = vd * cos_theta - vq * sin_theta;
*vbeta = vd * sin_theta + vq * cos_theta;
}
void PWM_DRIVER_OPENLOOP(float U_ALPHA, float U_BETA)
{
int IA = (int)(U_ALPHA * 1572.0f);
int IB = (int)(U_BETA * 1572.0f);
// A_PULS_DUTY = 0;
// A_MIUNS_DUTY = 250;//电流为负数
// B_PULS_DUTY = 250;
// B_MIUNS_DUTY = 0;//电流为负数90.00
// B_PULS_DUTY = (short)(IA + 510);
// A_MIUNS_DUTY = (short)(-IA + 510);
if (U_ALPHA == 0) {
A_PULS_DUTY = 500;
A_MIUNS_DUTY = 500;
}
if (U_BETA == 0) {
B_PULS_DUTY = 500;
B_MIUNS_DUTY = 500;
}
if (U_ALPHA < 0) {
A_PULS_DUTY = 0;
A_MIUNS_DUTY = (short)(-IA + 510);
}
if (U_ALPHA > 0) {
A_MIUNS_DUTY = 0;
A_PULS_DUTY = (short)(IA + 510);
}
if (U_BETA > 0) {
B_MIUNS_DUTY = 0;
B_PULS_DUTY = (short)(IB + 510);
}
if (U_BETA < 0) {
B_PULS_DUTY = 0;
B_MIUNS_DUTY = (short)(-IB + 510);
}
}
bool IRAM_ATTR foc_cal_cb(void *user_data)
{
TaskHandle_t task_handle = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup = pdFALSE;
if (task_handle != NULL) {
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
esp_err_t foc_openloop_start(void)
{
if (s_foc_task_handle == NULL) {
BaseType_t task_ok = xTaskCreatePinnedToCore(foc_task,
"foc_task",
4096,
NULL,
configMAX_PRIORITIES - 2,
&s_foc_task_handle,
0);
if (task_ok != pdPASS) {
return ESP_ERR_NO_MEM;
}
}
// if (!stepper_motor_output_is_enabled()) {
// stepper_motor_output_enable();
// }
foc_update_output_once();
ESP_RETURN_ON_ERROR(foc_timer_init(FOC_TIMER_DEFAULT_HZ, foc_cal_cb, s_foc_task_handle),
TAG_FOC_CTRL,
"foc timer init failed");
ESP_RETURN_ON_ERROR(foc_timer_start(),
TAG_FOC_CTRL,
"foc timer start failed");
return ESP_OK;
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef __FOC_H__
#define __FOC_H__
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "esp_check.h"
extern volatile short Motor_Speed;
extern volatile short A_PULS_DUTY;
extern volatile short A_MIUNS_DUTY;
extern volatile short B_PULS_DUTY;
extern volatile short B_MIUNS_DUTY;
extern volatile float Ud;
extern volatile float Uq;
extern volatile float theta;
extern volatile float U_ALPHA;
extern volatile float U_BETA;
void park(float ia, float ib, float theta, float *id, float *iq);
void inv_park(float vd, float vq, float theta, float *valpha, float *vbeta);
void PWM_DRIVER_OPENLOOP(float U_ALPHA, float U_BETA);
bool foc_cal_cb(void *user_data);
esp_err_t foc_openloop_start(void);
#endif
+307
View File
@@ -0,0 +1,307 @@
// #include <MOTOR/motor.h>
// // #include "freertos/FreeRTOS.h"
// // #include "freertos/task.h"
// static const char *TAG_MOTOR = "MotorControl";
// /** 置位后 motor_run_for_ms 与哨兵停 2 秒会提前退出,用于随时停止哨兵并回正 */
// static volatile bool motor_stop_request = false;
// static bool flag = true;
// void angle_control_task(void *pvParameter);
// float current_angle = 0;
// float target_angle = 0;
// float motor_speed = 90;
// uint32_t motor_rpm = 10;
// uint32_t motor_pwm = 0;
// uint32_t PWM_QUEUE[2] = {MOTOR_60_PWM, MOTOR_90_PWM};
// uint32_t SPEED_QUEUE[2] = {MOTOR_60_SPEED, MOTOR_90_SPEED};
// TaskHandle_t angle_control_handle = NULL;
// // 电机控制结构体
// typedef struct
// {
// gpio_num_t ain1_pin;
// gpio_num_t ain2_pin;
// ledc_channel_t ain1_channel;
// ledc_channel_t ain2_channel;
// } motor_config_t;
// static motor_config_t motor = {
// .ain1_pin = MOTOR_AIN1_GPIO,
// .ain2_pin = MOTOR_AIN2_GPIO,
// .ain1_channel = LEDC_CHANNEL_AIN1,
// .ain2_channel = LEDC_CHANNEL_AIN2};
// // 初始化PWM
// void pwm_init(void)
// {
// // 配置PWM定时器
// ledc_timer_config_t ledc_timer = {
// .speed_mode = LEDC_MODE,
// .duty_resolution = LEDC_DUTY_RES,
// .timer_num = LEDC_TIMER,
// .freq_hz = LEDC_FREQ,
// .clk_cfg = LEDC_AUTO_CLK};
// ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// // 配置PWM通道
// ledc_channel_config_t ledc_channel_ain1 = {
// .speed_mode = LEDC_MODE,
// .channel = motor.ain1_channel,
// .timer_sel = LEDC_TIMER,
// .intr_type = LEDC_INTR_DISABLE,
// .gpio_num = motor.ain1_pin,
// .duty = 0,
// .hpoint = 0};
// ledc_channel_config_t ledc_channel_ain2 = {
// .speed_mode = LEDC_MODE,
// .channel = motor.ain2_channel,
// .timer_sel = LEDC_TIMER,
// .intr_type = LEDC_INTR_DISABLE,
// .gpio_num = motor.ain2_pin,
// .duty = 0,
// .hpoint = 0};
// ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_ain1));
// ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_ain2));
// }
// void gpio_init(void)
// {
// // 配置GPIO为输出模式[2](@ref)
// gpio_config_t io_conf = {
// .pin_bit_mask = (1ULL << motor.ain1_pin | motor.ain2_pin),
// .mode = GPIO_MODE_OUTPUT,
// .pull_up_en = GPIO_PULLUP_DISABLE,
// .pull_down_en = GPIO_PULLDOWN_ENABLE,
// .intr_type = GPIO_INTR_DISABLE};
// gpio_config(&io_conf);
// }
// /**
// * 按固定时长运行电机(可被 motor_stop_request 中断,收到停止时立即停)
// * @param pwm 电机 PWM,正负表示方向
// * @param duration_ms 运行时长(毫秒)
// * @return 实际运行的毫秒数(若被中断则小于 duration_ms,用于按比例回正)
// */
// static uint32_t motor_run_for_ms(int32_t pwm, uint32_t duration_ms)
// {
// if (duration_ms == 0)
// return 0;
// TickType_t xWake = xTaskGetTickCount();
// TickType_t xStart = xWake;
// const TickType_t xPeriod = pdMS_TO_TICKS(1);
// TickType_t xEnd = xWake + pdMS_TO_TICKS(duration_ms);
// while (xTaskGetTickCount() < xEnd)
// {
// if (motor_stop_request)
// {
// motor_set_speed(0);
// return (uint32_t)pdTICKS_TO_MS(xTaskGetTickCount() - xStart);
// }
// motor_set_speed(pwm);
// vTaskDelayUntil(&xWake, xPeriod);
// }
// return duration_ms;
// }
// /** 可被 motor_stop_request 中断的延时(毫秒),用于哨兵停 2 秒等 */
// static void delay_ms_check_stop(uint32_t duration_ms)
// {
// TickType_t xWake = xTaskGetTickCount();
// TickType_t xEnd = xWake + pdMS_TO_TICKS(duration_ms);
// while (xTaskGetTickCount() < xEnd)
// {
// if (motor_stop_request)
// return;
// vTaskDelay(pdMS_TO_TICKS(1));
// }
// }
// void motor_request_stop(void)
// {
// motor_stop_request = true;
// }
// static void motor_clear_stop_request(void)
// {
// motor_stop_request = false;
// }
// // 设置电机速度和方向
// void motor_set_speed(int32_t speed)
// {
// speed = -speed;
// if (speed == 0)
// {
// // 停止电机
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, 0);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, 0);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// else if (speed > 0)
// {
// // 正转
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, speed * 1023 / MOTOR_MAX_PWM);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, 0);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// else
// {
// // 反转
// speed = -speed;
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, 0);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, speed * 1023 / MOTOR_MAX_PWM);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// /* motor_speed = (speed < 0) ? -speed : speed; */
// // ESP_LOGI(TAG_MOTOR, "Motor speed set to: %ld", speed);
// }
// // 设置目标角度
// void motor_set_angle(float angle)
// {
// set_flag(false);
// target_angle = angle;
// int angle_milli = (int)(target_angle * 1000.0f);
// ESP_LOGI(TAG_MOTOR, "Target angle set to: %d.%03d degrees", angle_milli / 1000, abs(angle_milli % 1000));
// }
// // 转速控制函数,单位为度每秒
// void motor_set_motor_speed(uint32_t speed)
// {
// motor_speed = speed;
// ESP_LOGI(TAG_MOTOR, "Motor_speed set to: %lu", speed);
// }
// // 电机初始化
// void motor_init(void)
// {
// gpio_init();
// pwm_init();
// target_angle = 0;
// current_angle = 0;
// motor_set_motor_speed(MOTOR_90_SPEED);
// /* motor_set_speed(MOTOR_30_PWM);
// vTaskDelay(pdMS_TO_TICKS(12000));
// motor_set_speed(0); */
// xTaskCreate(angle_control_task, "angle_control", 4096, NULL, 5, &angle_control_handle);
// }
// // 角度控制任务(需要编码器反馈,这里是开环控制示例)
// void angle_control_task(void *pvParameter)
// {
// TickType_t xLastWakeTime = xTaskGetTickCount();
// const TickType_t xControlPeriod = pdMS_TO_TICKS(1); // pdMS_TO_TICKS(20)
// ESP_LOGI(TAG_MOTOR, "Starting angle control task");
// uint32_t frist_back_time = 450;
// uint32_t pwm_value = 42;//53
// uint32_t back_time_period = 2000;//ms -- 5800
// for (size_t i = 0; i < 1; i++)
// // while(1)
// {
// motor_set_speed(pwm_value );
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(800));
// motor_set_speed(0);
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
// motor_set_speed(-pwm_value );
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(frist_back_time)); // 150
// ESP_LOGE("angle_task","back_time :%lu",frist_back_time);
// motor_set_speed(0);
// }
// uint32_t bits = 0;
// // int target_position = 0;
// bool motor_run_flag = false;
// MOTOR_STATE motor_state;
// while (1)
// {
// // 必须是有效的任务句柄
// BaseType_t notify_ret = xTaskNotifyWait(0x00000000, ULONG_MAX, &bits, 0); // pdMS_TO_TICKS(100)
// if (notify_ret == pdPASS)
// {
// motor_run_flag = true;
// if (bits == 0x00)
// {
// motor_clear_stop_request();
// motor_set_speed(0);
// }
// else if (bits == 0x01)
// {
// motor_run_for_ms(-pwm_value , 600);//200
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(back_time_period));
// motor_run_for_ms(pwm_value , 450);
// motor_set_speed(0);
// }
// else if (bits == 0x02)
// {
// motor_run_for_ms(pwm_value , 600);//110
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(back_time_period));
// motor_run_for_ms(-pwm_value,450);//310
// motor_set_speed(0);
// }
// else if (bits == 0x05)
// {
// motor_run_for_ms(-pwm_value, 150);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(pwm_value, 150);
// motor_set_speed(0);
// }
// else if (bits == 0x06)
// {
// motor_run_for_ms(pwm_value , 150);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(-pwm_value , 115);
// motor_set_speed(0);
// }
// else if (bits == 0x07)
// {
// motor_run_for_ms(-pwm_value , 1000);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(pwm_value , 900);
// motor_set_speed(0);
// }
// else if (bits == 0x11)
// {
// motor_run_for_ms(-pwm_value , 10);
// motor_set_speed(0);
// }
// else if (bits == 0x12)
// {
// motor_run_for_ms(pwm_value , 10);
// motor_set_speed(0);
// }
// else
// {
// frist_back_time = bits;
// // ESP_LOGE("angle_task","back_time :%lu",back_time);
// }
// }
// }
// }
// static void set_flag(bool state)
// {
// flag = state;
// }
// bool get_flag(void)
// {
// return flag;
// }
+104
View File
@@ -0,0 +1,104 @@
// #ifndef _MOTOR_H_
// #define _MOTOR_H_
// #ifdef __cplusplus
// extern "C" {
// #endif
// #include <math.h>
// #include <stdio.h>
// #include "freertos/FreeRTOS.h"
// #include "freertos/task.h"
// #include "freertos/queue.h"
// #include "driver/ledc.h"
// #include "driver/gpio.h"
// #include "esp_log.h"
// #include "esp_timer.h"
// #include <string.h>
// #include "nvs.h"
// #include "nvs_flash.h"
// #include <stdint.h>
// void motor_init(void);
// void motor_set_speed(int32_t speed);
// void motor_set_angle(float angle); //外部接口 -60 主驾 +60副驾
// void motor_set_motor_speed(uint32_t speed); //调整目标转速
// static void set_flag(bool state);
// bool get_flag(void);
// // 用于标识角度数据的键名
// #define STORAGE_NAMESPACE "motor_data"
// #define ANGLE_KEY "current_angle"
// // DRV8837引脚定义
// #define MOTOR_AIN1_GPIO GPIO_NUM_1
// #define MOTOR_AIN2_GPIO GPIO_NUM_2
// // #define MOTOR_AIN1_GPIO GPIO_NUM_9
// // #define MOTOR_AIN2_GPIO GPIO_NUM_46
// //#define MOTOR_BIN1_GPIO GPIO_NUM_14 // 可选,用于第二个电机
// //#define MOTOR_BIN2_GPIO GPIO_NUM_15 // 可选,用于第二个电机
// // PWM配置
// #define LEDC_TIMER LEDC_TIMER_0
// #define LEDC_MODE LEDC_LOW_SPEED_MODE
// #define LEDC_CHANNEL_AIN1 LEDC_CHANNEL_0
// #define LEDC_CHANNEL_AIN2 LEDC_CHANNEL_1
// #define LEDC_DUTY_RES LEDC_TIMER_10_BIT // 10位分辨率(0-1023)
// #define LEDC_FREQ 1000 // 1kHz PWM频率
// // 电机参数
// #define MOTOR_MAX_PWM 100
// #define MOTOR_90_SPEED 90
// #define MOTOR_90_PWM 42
// #define MOTOR_60_SPEED 60
// #define MOTOR_60_PWM 53 //30 50
// // #define MOTOR_30_SPEED 30
// // #define MOTOR_50_SPEED 50
// // #define MOTOR_50_PWM 44
// // #define MOTOR_45_SPEED 45
// // #define MOTOR_45_PWM 34
// // #define MOTOR_40_SPEED 40
// // #define MOTOR_40_PWM 28
// // #define MOTOR_36_SPEED 36
// // #define MOTOR_36_PWM 25
// // #define MOTOR_30_SPEED 30
// // #define MOTOR_30_PWM 21
// #define ANGLE_LIMIT 3
// extern TaskHandle_t angle_control_handle;
// typedef enum
// {
// motor_stop = 0 ,
// motor_turn_to_right,
// motor_turn_to_left,
// }MOTOR_STATE;
// // typedef enum
// // {
// // position_zero = 0,
// // position_right_15,//右边偏移15度
// // position_right_30,//右边偏移30度
// // position_right_60,//右边偏移60度
// // position_left_15,//左边偏移15度
// // position_left_30,//左边偏移30度
// // position_left_60,//左边偏移60度
// // position_left_180,//左边偏移180度
// // position_left_120,//左边偏移180度
// // position_left_240,//左边偏移180度
// // }MOTOR_POSITION;
// void motor_turn_aroud(uint8_t cmd);
// /** 请求电机立即停止(用于哨兵模式等可中断场景) */
// void motor_request_stop(void);
// #ifdef __cplusplus
// }
// #endif
// #endif /* TEST_H_ */
+413
View File
@@ -0,0 +1,413 @@
#include "motor_control.h"
#include "freertos/projdefs.h"
#include "freertos/queue.h"
#include "step_motor_init.h"
#include "motor_io.h"
#include "foc.h"
#include <limits.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_check.h"
#include "esp_log.h"
#include "BLE/ble.h"
static const char *TAG_MOTOR_CTL = "motor_control";
TaskHandle_t motor_control_handle = NULL;
#define MOTOR_CMD_QUEUE_LEN 16
static QueueHandle_t s_motor_cmd_q = NULL;
typedef enum
{
MOTOR_LAT_UNKNOWN = 0,
MOTOR_LAT_LEFT,
MOTOR_LAT_RIGHT,
MOTOR_LAT_CENTER,
} motor_lateral_t;
/** 仅电机任务写、其它任务读;用于左右切换时是否插 0x0A */
static volatile motor_lateral_t s_motor_last_lateral = MOTOR_LAT_CENTER;
/** 最近一次成功入队的 play_sticker 方向字节;同字节+同侧向则不再重复入队(如已是 0x01 又来 0x01) */
static uint8_t s_last_play_sticker_direction = 0xFF;
// MOTOR_POSITION motor_position ;//记录步进电机的位置
MOTOR_POSITION motor_current_position = MOTOR_POSITION_ZERO;
MOTOR_POSITION motor_target_position = MOTOR_POSITION_ZERO;
MOTOR_DIR motor_dir = MOTOR_CW;
SemaphoreHandle_t motor_action_down_sem = NULL;
static volatile bool s_sentinel_motor_running = false;
static volatile bool s_sentinel_stop_requested = false;
#if MOTOR_WITH_MAGNET_RING
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 500,
.run_to_left_90 = 1000,
.run_to_left_180 = 1800,
.run_to_right_15 = 250,
.run_to_right_30 = 500,
.run_to_right_180 = 1800,
};
uint16_t OB_BACK_TIME = 130;
#elif OLD_MOTOR
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 500,
.run_to_left_90 = 750,
.run_to_left_180 = 1300,
.run_to_right_15 = 250,
.run_to_right_30 = 500,
.run_to_right_180 = 1300,
};
uint16_t OB_BACK_TIME = 55;
#else
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 450,
.run_to_left_90 = 1250,
.run_to_left_180 = 1950,
.run_to_right_15 = 250,
.run_to_right_30 = 450,
.run_to_right_180 = 1950,
};
uint16_t OB_BACK_TIME = 55;
#endif
static void motor_run_steps(uint32_t command);
/** 与上次已在电机任务里执行完的 notify 值相同则忽略,避免 BLE 连发 / completed_cb 重复补发导致同向连转 */
static uint32_t s_last_executed_motor_notify = UINT32_MAX;
static bool motor_cmd_is_left(uint32_t cmd)
{
return cmd == 0x01u || cmd == 0x05u || cmd == 0x07u;
}
static bool motor_cmd_is_right(uint32_t cmd)
{
return cmd == 0x02u || cmd == 0x06u || cmd == 0x08u;
}
static void motor_update_lateral_after_cmd(uint32_t cmd)
{
if (motor_cmd_is_left(cmd))
{
s_motor_last_lateral = MOTOR_LAT_LEFT;
}
else if (motor_cmd_is_right(cmd))
{
s_motor_last_lateral = MOTOR_LAT_RIGHT;
}
else if (cmd == 0x00u || cmd == 0x0Au || cmd == 0x0Bu)
{
s_motor_last_lateral = MOTOR_LAT_CENTER;
}
}
bool motor_post_cmd(uint32_t cmd)
{
if (s_motor_cmd_q == NULL)
{
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: queue not ready cmd=0x%02lX", (unsigned long)cmd);
return false;
}
if (xQueueSend(s_motor_cmd_q, &cmd, 0) != pdPASS)
{
uint32_t dropped = 0;
(void)xQueueReceive(s_motor_cmd_q, &dropped, 0);
if (xQueueSend(s_motor_cmd_q, &cmd, 0) != pdPASS)
{
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: enqueue failed cmd=0x%02lX", (unsigned long)cmd);
return false;
}
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: queue full dropped=0x%02lX enq=0x%02lX",
(unsigned long)dropped, (unsigned long)cmd);
}
return true;
}
bool motor_post_play_sticker_direction(uint8_t direction)
{
const uint32_t cmd = (uint32_t)direction;
/* 0x00:协议表示「本条不追加转向」;若头仍在左/右侧,必须用 0x0A 物理回中,
* 不能发 0x00(openloop stop 与回中语义不同),否则左播途中接 0x10/0x00 会丢回中。 */
if (cmd == 0x00u)
{
const motor_lateral_t last = s_motor_last_lateral;
if (last == MOTOR_LAT_LEFT || last == MOTOR_LAT_RIGHT)
{
if (!motor_post_cmd(0x0Au))
{
return false;
}
s_last_play_sticker_direction = 0xFF;
return true;
}
return true;
}
if (cmd == 0x0Au || cmd == 0x0Bu)
{
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = 0xFF;
return true;
}
if (!motor_cmd_is_left(cmd) && !motor_cmd_is_right(cmd))
{
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = (uint8_t)cmd;
return true;
}
const motor_lateral_t last = s_motor_last_lateral;
/* 侧向未变且与上次下发的方向字节相同:不重复入队(如当前已是 0x01 姿态又来 0x01) */
if ((motor_cmd_is_left(cmd) && last == MOTOR_LAT_LEFT) || (motor_cmd_is_right(cmd) && last == MOTOR_LAT_RIGHT))
{
if ((uint8_t)cmd == s_last_play_sticker_direction)
{
ESP_LOGI(TAG_MOTOR_CTL, "skip redundant play_sticker direction 0x%02lX (same as last)", cmd);
return true;
}
}
const bool need_home =
(last == MOTOR_LAT_LEFT && motor_cmd_is_right(cmd)) || (last == MOTOR_LAT_RIGHT && motor_cmd_is_left(cmd));
if (need_home && !motor_post_cmd(0x0Au))
{
return false;
}
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = (uint8_t)cmd;
return true;
}
void motor_post_cmd_from_isr(uint32_t cmd, BaseType_t *pxHigherPriorityTaskWoken)
{
if (s_motor_cmd_q == NULL)
{
return;
}
if (xQueueSendFromISR(s_motor_cmd_q, &cmd, pxHigherPriorityTaskWoken) != pdPASS)
{
uint32_t dropped = 0;
(void)xQueueReceiveFromISR(s_motor_cmd_q, &dropped, pxHigherPriorityTaskWoken);
(void)xQueueSendFromISR(s_motor_cmd_q, &cmd, pxHigherPriorityTaskWoken);
}
}
void motor_control_init(void)
{
// PWM 初始化 任务创建
//motor_io_init();
stepper_motor_init();
s_motor_cmd_q = xQueueCreate(MOTOR_CMD_QUEUE_LEN, sizeof(uint32_t));
if (s_motor_cmd_q == NULL)
{
ESP_LOGE(TAG_MOTOR_CTL, "motor cmd queue create failed");
}
// 固定到 CPU1,避免与 CPU0 上的 LVGL task 竞争;优先级降为 5
xTaskCreatePinnedToCore(motor_control_task, "motor_control_task", 4096, NULL, 8, &motor_control_handle, 0);
}
void motor_control_task(void *pvParameter)
{
uint32_t bits = 0;
motor_action_down_sem = xSemaphoreCreateBinary();
xSemaphoreGive(motor_action_down_sem);
(void)pvParameter;
s_motor_last_lateral = MOTOR_LAT_CENTER;
motor_current_position =MOTOR_POSITION_ZERO;
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_180));
motor_current_position = MOTOR_POSITION_LEFT_180;
stepper_motor_output_disable();
ESP_LOGW("TAG_MOTOR_CTL", "motor_control_task: TURNED LEFT DONE");
s_motor_last_lateral = MOTOR_LAT_LEFT;
vTaskDelay(pdMS_TO_TICKS(2000));
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_180));
stepper_motor_output_disable();
s_motor_last_lateral = MOTOR_LAT_CENTER;
motor_current_position = MOTOR_POSITION_ZERO;
ESP_LOGW("TAG_MOTOR_CTL", "motor_control_task: TURNED RIGHT DONE");
while (1)
{
if (s_motor_cmd_q == NULL)
{
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
if (xQueueReceive(s_motor_cmd_q, &bits, pdMS_TO_TICKS(20)) != pdPASS)
{
continue;
}
if (bits == s_last_executed_motor_notify)
{
ESP_LOGD(TAG_MOTOR_CTL, "skip duplicate motor cmd 0x%02lX", (unsigned long)bits);
continue;
}
motor_run_steps(bits);
s_last_executed_motor_notify = bits;
motor_update_lateral_after_cmd(bits);
}
}
static void motor_run_steps(uint32_t command)
{
uint32_t back_time_ms = 0;
if (command == 0x00)
{
stepper_motor_stop();
motor_current_position = MOTOR_POSITION_ZERO;
}
else if (command == 0x01)
{
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_30));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_30;
}
else if (command == 0x02)
{
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_30));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_30;
}
else if (command == 0x05)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_15 * 4);
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_15));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_15;
}
else if (command == 0x06)
{
// stepper_motor_run_steps(STEPPER_DIR_REVERSE, ANGLE_STEP_15 * 4);
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_15));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_15;
}
else if (command == 0x07)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_180 * 4);
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_180));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_180;
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
}
else if (command == 0x08)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_180 * 4);
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_180));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_180;
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
}
else if (command == 0x0A)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_30 * 4);
if(motor_current_position == MOTOR_POSITION_ZERO)back_time_ms = 0;
else if(motor_current_position == MOTOR_POSITION_LEFT_15 || motor_current_position == MOTOR_POSITION_LEFT_30 || motor_current_position == MOTOR_POSITION_LEFT_180)
{
motor_run_dir(MOTOR_CCW);
if(motor_current_position == MOTOR_POSITION_LEFT_15)back_time_ms = motor_run_time.run_to_right_15;
else if(motor_current_position == MOTOR_POSITION_LEFT_30)back_time_ms = motor_run_time.run_to_right_30;
else if(motor_current_position == MOTOR_POSITION_LEFT_180)back_time_ms = motor_run_time.run_to_right_180;
}
else if(motor_current_position == MOTOR_POSITION_RIGHT_15 || motor_current_position == MOTOR_POSITION_RIGHT_30|| motor_current_position == MOTOR_POSITION_RIGHT_180)
{
motor_run_dir(MOTOR_CW);
if(motor_current_position == MOTOR_POSITION_RIGHT_15)back_time_ms = motor_run_time.run_to_left_15;
else if(motor_current_position == MOTOR_POSITION_RIGHT_30)back_time_ms = motor_run_time.run_to_left_30;
else if(motor_current_position == MOTOR_POSITION_RIGHT_180)back_time_ms = motor_run_time.run_to_left_180;
}
if(back_time_ms != 0)
{
ESP_LOGW(TAG_MOTOR_CTL, "0X0A back_time_ms START,motor_current_position=%d,back_time_ms=%d",motor_current_position,back_time_ms);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(back_time_ms));
}
stepper_motor_output_disable();
ESP_LOGW(TAG_MOTOR_CTL, "0X0A back_time_ms END");
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
motor_current_position = MOTOR_POSITION_ZERO;
}
else if (command == 0x0B)
{
ESP_LOGE("MOTOR_CONTROL", "MOTOR_CONTROL_STOP");
vTaskDelay(pdMS_TO_TICKS(100));
// motor_run_dir(MOTOR_CW);
// stepper_motor_output_enable();
// vTaskDelay(pdMS_TO_TICKS(35));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_ZERO;
}
}
void motor_run_dir(MOTOR_DIR dir)
{
if(dir == MOTOR_CW)
{
motor_dir = MOTOR_CW;
}
if(dir == MOTOR_CCW)
{
motor_dir = MOTOR_CCW;
}
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef __MOTOR_CONTROL_H__
#define __MOTOR_CONTROL_H__
#ifdef __cplisplus
extern "C" {
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#define MOTOR_WITH_MAGNET_RING 0 //带磁环的时候
#define OLD_MOTOR 0 //旧电机参数
typedef enum
{
MOTOR_POSITION_ZERO = 0,
MOTOR_POSITION_LEFT_15 ,
MOTOR_POSITION_RIGHT_15 ,
MOTOR_POSITION_LEFT_30 ,
MOTOR_POSITION_RIGHT_30 ,
MOTOR_POSITION_LEFT_90 ,
MOTOR_POSITION_RIGHT_90 ,
MOTOR_POSITION_LEFT_180 ,
MOTOR_POSITION_RIGHT_180,
MOTOR_POSITION_LEFT_2 ,//微步
}MOTOR_POSITION;
typedef enum
{
MOTOR_CW,
MOTOR_CCW,
}MOTOR_DIR;
typedef struct
{
uint32_t run_to_left_15;
uint32_t run_to_left_30;
uint32_t run_to_left_90;
uint32_t run_to_left_180;
uint32_t run_to_right_15;
uint32_t run_to_right_30;
uint32_t run_to_right_180;
}MOTOR_RUN_TIME;
extern TaskHandle_t motor_control_handle;
extern MOTOR_DIR motor_dir;
extern MOTOR_POSITION motor_current_position ;
extern MOTOR_POSITION motor_target_position ;
extern SemaphoreHandle_t motor_action_down_sem;
void motor_control_init(void);
void motor_control_task(void *pvParameter);
void motor_run_dir(MOTOR_DIR dir);
/** 串行入队执行;满则丢最旧再入队,避免 Overwrite 丢命令 */
bool motor_post_cmd(uint32_t cmd);
/** play_sticker 方向:左右切换时先排队 0x0A;0x00 在头仍左/右时排队 0x0A;与上次同字节且侧向未变则跳过 */
bool motor_post_play_sticker_direction(uint8_t direction);
void motor_post_cmd_from_isr(uint32_t cmd, BaseType_t *pxHigherPriorityTaskWoken);
#ifdef __cplusplus
}
#endif
#endif
+194
View File
@@ -0,0 +1,194 @@
#include "motor_io.h"
#include <stdbool.h>
#include "MOTOR/motor_control.h"
#include "step_motor_init.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "motor_io";
static TaskHandle_t s_motor_io_task_handle = NULL;
static volatile bool s_stop_transition_enabled = false;
static volatile int s_stop_from_14 = 0;
static volatile int s_stop_from_47 = 0;
static volatile int s_stop_to_14 = 0;
static volatile int s_stop_to_47 = 0;
volatile int s_io14_last_level ;
volatile int s_io47_last_level ;
volatile int s_io14_now_level ;
volatile int s_io47_now_level ;
static void motor_io_log_levels(const char *reason, int level_14, int level_47)
{
ESP_LOGI(TAG, "%s GPIO14=%d GPIO47=%d", reason, level_14, level_47);
}
static void IRAM_ATTR motor_io_isr_handler(void *arg)
{
(void)arg;
BaseType_t hpw = pdFALSE;
s_io14_now_level = motor_io_get_level_14();
s_io47_now_level = motor_io_get_level_47();
if (motor_control_handle != NULL)
{
if(s_io14_last_level == 0 && s_io47_last_level == 1)
{
if(s_io14_now_level == 1 && s_io47_now_level == 1)
{
//发送停止信号
if(motor_current_position == MOTOR_POSITION_LEFT_2)
{
stepper_motor_output_disable();
motor_post_cmd_from_isr((uint32_t)0x0Bu, &hpw);
}
}
}
if(s_io14_last_level == 0 && s_io47_last_level == 1)
{
if(s_io14_now_level == 0 && s_io47_now_level == 0)
{
//发送停止信号
// xTaskNotifyFromISR(motor_control_handle,(uint32_t)0x0B,eSetValueWithOverwrite,NULL);
}
}
}
s_io14_last_level = s_io14_now_level;
s_io47_last_level = s_io47_now_level;
if (s_motor_io_task_handle != NULL)
{
vTaskNotifyGiveFromISR(s_motor_io_task_handle, &hpw);
}
if (hpw == pdTRUE)
{
portYIELD_FROM_ISR();
}
}
int motor_io_get_level_14(void)
{
return gpio_get_level(MOTOR_IO_PIN_14);
}
int motor_io_get_level_47(void)
{
return gpio_get_level(MOTOR_IO_PIN_47);
}
void motor_io_get_levels(int *level_14, int *level_47)
{
if (level_14 != NULL)
{
*level_14 = motor_io_get_level_14();
}
if (level_47 != NULL)
{
*level_47 = motor_io_get_level_47();
}
}
void motor_io_set_stop_transition(int from_14, int from_47, int to_14, int to_47)
{
s_stop_from_14 = from_14;
s_stop_from_47 = from_47;
s_stop_to_14 = to_14;
s_stop_to_47 = to_47;
s_stop_transition_enabled = true;
}
void motor_io_clear_stop_transition(void)
{
s_stop_transition_enabled = false;
}
static void motor_io_monitor_task(void *pvParameter)
{
(void)pvParameter;
int last_level_14 = motor_io_get_level_14();
int last_level_47 = motor_io_get_level_47();
motor_io_log_levels("初始电平:", last_level_14, last_level_47);
while (1)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
int level_14 = motor_io_get_level_14();
int level_47 = motor_io_get_level_47();
if ((level_14 != last_level_14) || (level_47 != last_level_47))
{
motor_io_log_levels("检测到电平变化:", level_14, level_47);
if (s_stop_transition_enabled &&
(last_level_14 == s_stop_from_14) &&
(last_level_47 == s_stop_from_47) &&
(level_14 == s_stop_to_14) &&
(level_47 == s_stop_to_47))
{
ESP_LOGI(TAG, "命中停机跳变: GPIO14=%d GPIO47=%d -> GPIO14=%d GPIO47=%d",
last_level_14,
last_level_47,
level_14,
level_47);
stepper_motor_stop();
}
last_level_14 = level_14;
last_level_47 = level_47;
}
}
}
void motor_io_init(void)
{
const gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << MOTOR_IO_PIN_14) | (1ULL << MOTOR_IO_PIN_47),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_ANYEDGE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
if (s_motor_io_task_handle == NULL)
{
xTaskCreatePinnedToCore(motor_io_monitor_task,
"motor_io_task",
MOTOR_IO_TASK_STACK_SIZE,
NULL,
MOTOR_IO_TASK_PRIORITY,
&s_motor_io_task_handle,
1);
}
esp_err_t ret = gpio_install_isr_service(0);
if ((ret != ESP_OK) && (ret != ESP_ERR_INVALID_STATE))
{
ESP_ERROR_CHECK(ret);
}
ESP_ERROR_CHECK(gpio_isr_handler_add(MOTOR_IO_PIN_14, motor_io_isr_handler, NULL));
ESP_ERROR_CHECK(gpio_isr_handler_add(MOTOR_IO_PIN_47, motor_io_isr_handler, NULL));
s_io14_last_level = motor_io_get_level_14();
s_io47_last_level = motor_io_get_level_47();
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef __MOTOR_IO_H__
#define __MOTOR_IO_H__
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MOTOR_IO_PIN_14 GPIO_NUM_14
#define MOTOR_IO_PIN_47 GPIO_NUM_47
#define MOTOR_IO_TASK_STACK_SIZE 4096
#define MOTOR_IO_TASK_PRIORITY 8 // 优先级 ---
void motor_io_init(void);
int motor_io_get_level_14(void);
int motor_io_get_level_47(void);
void motor_io_get_levels(int *level_14, int *level_47);
void motor_io_set_stop_transition(int from_14, int from_47, int to_14, int to_47);
void motor_io_clear_stop_transition(void);
#ifdef __cplusplus
}
#endif
#endif
+153
View File
@@ -0,0 +1,153 @@
#include "motor_timer.h"
#include <math.h>
#include "driver/gptimer.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_log.h"
static const char *TAG_FOC = "foc_timer"; //打印标签
static gptimer_handle_t s_foc_timer = NULL; //定时器timer句柄
static foc_timer_isr_cb_t s_foc_timer_user_cb = NULL; //用户回调函数--FOC计算
static void *s_foc_timer_user_data = NULL; //用户回调传入一个私有参数
static uint32_t s_foc_timer_freq_hz = FOC_TIMER_DEFAULT_HZ; //定时器频率 3K hz
static bool s_foc_timer_started = false; //记录定时器启动标记
static bool IRAM_ATTR foc_timer_on_alarm_cb(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *edata,
void *user_data)
{
(void)timer;
(void)edata;
(void)user_data;
if (s_foc_timer_user_cb) {
return s_foc_timer_user_cb(s_foc_timer_user_data);
}
return false;
}
static esp_err_t foc_timer_apply_alarm_locked(uint32_t freq_hz)
{
if (freq_hz == 0 || s_foc_timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
uint64_t alarm_count = 1000000ULL / freq_hz;
if (alarm_count == 0) {
alarm_count = 1;
}
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = alarm_count,
.flags.auto_reload_on_alarm = true,
};
return gptimer_set_alarm_action(s_foc_timer, &alarm_config);
}
esp_err_t foc_timer_init(uint32_t freq_hz, foc_timer_isr_cb_t cb, void *user_data)
{
if (freq_hz == 0) {
freq_hz = FOC_TIMER_DEFAULT_HZ;
}
if (s_foc_timer != NULL) {
s_foc_timer_user_cb = cb;
s_foc_timer_user_data = user_data;
s_foc_timer_freq_hz = freq_hz;
return foc_timer_apply_alarm_locked(freq_hz);
}
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000,
};
ESP_RETURN_ON_ERROR(gptimer_new_timer(&timer_config, &s_foc_timer), TAG_FOC, "create gptimer failed");
gptimer_event_callbacks_t cbs = {
.on_alarm = foc_timer_on_alarm_cb,
};
ESP_RETURN_ON_ERROR(gptimer_register_event_callbacks(s_foc_timer, &cbs, NULL), TAG_FOC, "register gptimer callback failed");
s_foc_timer_user_cb = cb;
s_foc_timer_user_data = user_data;
s_foc_timer_freq_hz = freq_hz;
s_foc_timer_started = false;
ESP_RETURN_ON_ERROR(foc_timer_apply_alarm_locked(freq_hz), TAG_FOC, "set gptimer alarm failed");
esp_err_t err = gptimer_enable(s_foc_timer);
if ((err != ESP_OK) && (err != ESP_ERR_INVALID_STATE)) {
return err;
}
ESP_LOGI(TAG_FOC, "FOC timer init done, freq=%lu Hz", (unsigned long)s_foc_timer_freq_hz);
return ESP_OK;
}
esp_err_t foc_timer_set_frequency(uint32_t freq_hz)
{
if (freq_hz == 0 || s_foc_timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
bool restart = s_foc_timer_started;
if (restart) {
ESP_RETURN_ON_ERROR(gptimer_stop(s_foc_timer), TAG_FOC, "stop gptimer before retune failed");
s_foc_timer_started = false;
}
ESP_RETURN_ON_ERROR(foc_timer_apply_alarm_locked(freq_hz), TAG_FOC, "retune gptimer alarm failed");
s_foc_timer_freq_hz = freq_hz;
if (restart) {
ESP_RETURN_ON_ERROR(gptimer_start(s_foc_timer), TAG_FOC, "restart gptimer failed");
s_foc_timer_started = true;
}
ESP_LOGI(TAG_FOC, "FOC timer frequency set to %lu Hz", (unsigned long)s_foc_timer_freq_hz);
return ESP_OK;
}
esp_err_t foc_timer_start(void)
{
if (s_foc_timer == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (s_foc_timer_started) {
return ESP_OK;
}
ESP_RETURN_ON_ERROR(gptimer_set_raw_count(s_foc_timer, 0), TAG_FOC, "reset gptimer count failed");
esp_err_t err = gptimer_start(s_foc_timer);
if ((err != ESP_OK) && (err != ESP_ERR_INVALID_STATE)) {
return err;
}
s_foc_timer_started = true;
ESP_LOGI(TAG_FOC, "FOC timer started");
return ESP_OK;
}
esp_err_t foc_timer_stop(void)
{
if (s_foc_timer == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (!s_foc_timer_started) {
return ESP_OK;
}
ESP_RETURN_ON_ERROR(gptimer_stop(s_foc_timer), TAG_FOC, "stop gptimer failed");
s_foc_timer_started = false;
ESP_LOGI(TAG_FOC, "FOC timer stopped");
return ESP_OK;
}
uint32_t foc_timer_get_frequency(void)
{
return s_foc_timer_freq_hz;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __MOTOR_TIMER_H__
#define __MOTOR_TIMER_H__
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "config.h"
typedef bool (*foc_timer_isr_cb_t)(void *user_data);
#define FOC_TIMER_DEFAULT_HZ 8000U
esp_err_t foc_timer_init(uint32_t freq_hz, foc_timer_isr_cb_t cb, void *user_data);
esp_err_t foc_timer_set_frequency(uint32_t freq_hz);
esp_err_t foc_timer_start(void);
esp_err_t foc_timer_stop(void);
uint32_t foc_timer_get_frequency(void);
#endif
+276
View File
@@ -0,0 +1,276 @@
#include "step_motor_init.h"
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_timer.h"
static mcpwm_timer_handle_t s_timer;
static mcpwm_oper_handle_t s_oper[2];
static mcpwm_cmpr_handle_t s_cmpr[4]; /* [0]=A+ [1]=A- [2]=B+ [3]=B- */
static mcpwm_gen_handle_t s_gen[4];
static esp_timer_handle_t s_openloop_timer;
static uint32_t s_pwm_period_ticks = STEPPER_PWM_RESOL_HZ / STEPPER_PWM_FREQ_HZ;
static bool s_running = false;
static bool s_output_enabled = false;
static bool s_openloop_running = false;
static uint8_t s_openloop_step_idx = 0;
static stepper_motor_direction_t s_direction = STEPPER_DIR_FORWARD;
static const char *TAG = "stepper_motor";
static const uint16_t s_openloop_phase_table[4][4] = {
{ STEPPER_STARTUP_DUTY_RAW, 0, STEPPER_STARTUP_DUTY_RAW, 0 },
{ 0, STEPPER_STARTUP_DUTY_RAW, STEPPER_STARTUP_DUTY_RAW, 0 },
{ 0, STEPPER_STARTUP_DUTY_RAW, 0, STEPPER_STARTUP_DUTY_RAW },
{ STEPPER_STARTUP_DUTY_RAW, 0, 0, STEPPER_STARTUP_DUTY_RAW },
};
static uint32_t duty_raw_to_ticks(uint16_t duty_raw)
{
if (duty_raw > STEPPER_DUTY_MAX) {
duty_raw = STEPPER_DUTY_MAX;
}
return ((uint64_t)duty_raw * s_pwm_period_ticks) / STEPPER_DUTY_MAX;
// return ((uint64_t)duty_raw * s_pwm_period_ticks);
// return ((uint64_t)duty_raw );
}
static void apply_openloop_phase(uint8_t step_idx)
{
const uint16_t *phase = s_openloop_phase_table[step_idx & 0x03];
stepper_motor_set_duty_raw(phase[0], phase[1], phase[2], phase[3]);
}
static void stepper_motor_step_once(stepper_motor_direction_t direction)
{
if (direction == STEPPER_DIR_FORWARD) {
s_openloop_step_idx = (uint8_t)((s_openloop_step_idx + 1) & 0x03);
} else {
s_openloop_step_idx = (uint8_t)((s_openloop_step_idx + 3) & 0x03);
}
apply_openloop_phase(s_openloop_step_idx);
}
static void openloop_timer_cb(void *arg)
{
(void)arg;
stepper_motor_step_once(s_direction);
}
void stepper_motor_output_enable(void)
{
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[0], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[1], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[2], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[3], -1, true));
s_output_enabled = true;
}
void stepper_motor_output_disable(void)
{
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[0], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[1], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[2], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[3], 0, true));
s_output_enabled = false;
}
bool stepper_motor_output_is_enabled(void)
{
return s_output_enabled;
}
stepper_motor_direction_t stepper_motor_get_direction(void)
{
return s_direction;
}
void stepper_motor_set_duty_raw(uint16_t a_plus, uint16_t a_minus, uint16_t b_plus, uint16_t b_minus)
{
if (!s_running) {
return;
}
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[0], duty_raw_to_ticks(a_plus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[1], duty_raw_to_ticks(a_minus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[2], duty_raw_to_ticks(b_plus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[3], duty_raw_to_ticks(b_minus)));
}
void stepper_motor_openloop_start_dir(stepper_motor_direction_t direction)
{
if (s_openloop_timer == NULL) {
esp_timer_create_args_t timer_args = {
.callback = openloop_timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "step_ol",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_openloop_timer));
}
if (!s_running) {
return;
}
s_direction = (direction == STEPPER_DIR_REVERSE) ? STEPPER_DIR_REVERSE : STEPPER_DIR_FORWARD;
if (s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
if (!s_output_enabled) {
stepper_motor_output_enable();
}
s_openloop_step_idx = 0;
apply_openloop_phase(s_openloop_step_idx);
const uint64_t period_us = 1000000ULL / (uint64_t)(STEPPER_OPENLOOP_STEP_HZ * 4U);
ESP_ERROR_CHECK(esp_timer_start_periodic(s_openloop_timer, period_us));
s_openloop_running = true;
ESP_LOGI(TAG,
"openloop start: dir=%s duty=%u step_hz=%u",
s_direction == STEPPER_DIR_FORWARD ? "forward" : "reverse",
STEPPER_STARTUP_DUTY_RAW,
STEPPER_OPENLOOP_STEP_HZ);
}
void stepper_motor_openloop_start(void)
{
stepper_motor_openloop_start_dir(s_direction);
}
void stepper_motor_forward(void)
{
stepper_motor_openloop_start_dir(STEPPER_DIR_FORWARD);
}
void stepper_motor_reverse(void)
{
stepper_motor_openloop_start_dir(STEPPER_DIR_REVERSE);
}
void stepper_motor_run_steps(stepper_motor_direction_t direction, uint32_t steps)
{
if (!s_running || steps == 0) {
return;
}
if (s_openloop_timer != NULL && s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
s_direction = (direction == STEPPER_DIR_REVERSE) ? STEPPER_DIR_REVERSE : STEPPER_DIR_FORWARD;
if (!s_output_enabled) {
stepper_motor_output_enable();
}
for (uint32_t i = 0; i < steps; ++i) {
stepper_motor_step_once(s_direction);
vTaskDelay(pdMS_TO_TICKS(1000 / (STEPPER_OPENLOOP_STEP_HZ * 4U)));
}
stepper_motor_stop();
}
void stepper_motor_openloop_stop(void)
{
if (s_openloop_timer != NULL && s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
stepper_motor_set_duty_raw(0, 0, 0, 0);
stepper_motor_output_disable();
ESP_LOGI(TAG, "openloop stop");
}
void stepper_motor_stop(void)
{
stepper_motor_openloop_stop();
}
void stepper_motor_init(void)
{
gpio_config_t gpio_cfg = {
.pin_bit_mask = (1ULL << STEPPER_PIN_A1) | (1ULL << STEPPER_PIN_A2) |
(1ULL << STEPPER_PIN_B1) | (1ULL << STEPPER_PIN_B2),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
gpio_set_level(STEPPER_PIN_A1, 0);
gpio_set_level(STEPPER_PIN_A2, 0);
gpio_set_level(STEPPER_PIN_B1, 0);
gpio_set_level(STEPPER_PIN_B2, 0);
ESP_LOGI(TAG, "Step1: GPIO done");
mcpwm_timer_config_t timer_cfg = {
.group_id = STEPPER_MCPWM_GROUP,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = STEPPER_PWM_RESOL_HZ,
.period_ticks = s_pwm_period_ticks,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.flags = { .update_period_on_empty = true },
};
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_cfg, &s_timer));
ESP_LOGI(TAG, "Step2: timer created | pwm=%lu Hz | period=%lu",
(unsigned long)STEPPER_PWM_FREQ_HZ,
(unsigned long)s_pwm_period_ticks);
mcpwm_operator_config_t oper_cfg = { .group_id = STEPPER_MCPWM_GROUP };
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_cfg, &s_oper[0]));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(s_oper[0], s_timer));
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_cfg, &s_oper[1]));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(s_oper[1], s_timer));
ESP_LOGI(TAG, "Step3: operators connected");
mcpwm_comparator_config_t cmpr_cfg = { .flags.update_cmp_on_tez = true };
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[0], &cmpr_cfg, &s_cmpr[0]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[0], &cmpr_cfg, &s_cmpr[1]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[1], &cmpr_cfg, &s_cmpr[2]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[1], &cmpr_cfg, &s_cmpr[3]));
ESP_LOGI(TAG, "Step4: comparators created");
mcpwm_generator_config_t gen_cfg = { .gen_gpio_num = STEPPER_PIN_A1 };
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[0], &gen_cfg, &s_gen[0]));
gen_cfg.gen_gpio_num = STEPPER_PIN_A2;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[0], &gen_cfg, &s_gen[1]));
gen_cfg.gen_gpio_num = STEPPER_PIN_B1;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[1], &gen_cfg, &s_gen[2]));
gen_cfg.gen_gpio_num = STEPPER_PIN_B2;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[1], &gen_cfg, &s_gen[3]));
ESP_LOGI(TAG, "Step5: generators created");
for (int i = 0; i < 4; ++i) {
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(s_gen[i],
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),//MCPWM_GEN_ACTION_LOW
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(s_gen[i],
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
s_cmpr[i], MCPWM_GEN_ACTION_HIGH),//
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
ESP_LOGI(TAG, "Step6: pwm actions configured");
stepper_motor_output_disable();
ESP_ERROR_CHECK(mcpwm_timer_enable(s_timer));
ESP_ERROR_CHECK(mcpwm_timer_start_stop(s_timer, MCPWM_TIMER_START_NO_STOP));
s_running = true;
stepper_motor_set_duty_raw(0, 0, 0, 0);
ESP_LOGI(TAG, "MCPWM init done, motor standby on boot");
}
+50
View File
@@ -0,0 +1,50 @@
#ifndef __STEP_MOTOT_INIT_H__
#define __STEP_MOTOT_INIT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/mcpwm_prelude.h"
/* ---- 步进电机引脚 ---- */
#define STEPPER_PIN_A1 GPIO_NUM_20 /* A 相正 */
#define STEPPER_PIN_A2 GPIO_NUM_21 /* A 相负 */
#define STEPPER_PIN_B1 GPIO_NUM_23 /* B 相正 */
#define STEPPER_PIN_B2 GPIO_NUM_22 /* B 相负 */
/* ---- MCPWM 配置 ---- */
#define STEPPER_MCPWM_GROUP 0
#define STEPPER_PWM_RESOL_HZ 4000000U /* 定时器时钟分辨率 4 MHz */
#define STEPPER_PWM_FREQ_HZ 8000U /* PWM 载波频率 8 kHz */
#define STEPPER_DUTY_MAX 1000U /* 占空比满量程,和 A_PULS_DUTY 等变量一致 */
#define STEPPER_STARTUP_DUTY_RAW (STEPPER_DUTY_MAX / 2)
#define STEPPER_OPENLOOP_STEP_HZ 100U /* 上电演示转动的步进频率 */
#define STEPPER_DEFAULT_RUN_STEPS (STEPPER_OPENLOOP_STEP_HZ * 4U)
typedef enum {
STEPPER_DIR_FORWARD = 1,//原为 1
STEPPER_DIR_REVERSE = -1,//原为 -1
} stepper_motor_direction_t;
void stepper_motor_init(void);
void stepper_motor_set_duty_raw(uint16_t a_plus, uint16_t a_minus, uint16_t b_plus, uint16_t b_minus);
void stepper_motor_output_enable(void);
void stepper_motor_output_disable(void);
void stepper_motor_openloop_start(void);
void stepper_motor_openloop_start_dir(stepper_motor_direction_t direction);
void stepper_motor_forward(void);
void stepper_motor_reverse(void);
void stepper_motor_run_steps(stepper_motor_direction_t direction, uint32_t steps);
void stepper_motor_stop(void);
void stepper_motor_openloop_stop(void);
bool stepper_motor_output_is_enabled(void);
stepper_motor_direction_t stepper_motor_get_direction(void);
#ifdef __cplusplus
}
#endif
#endif /* __STEP_MOTOR_INIT_H__ */
+1290
View File
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
#ifndef MQTT_H
#define MQTT_H
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "mqtt_client.h"
#include "cjson.h"
#include "esp_timer.h"
#define DEVICE_FIRMWARE_VERSION "v1.0.3"//原为V1.0.0
void handle_received_data(const char* topic, int topic_len, const char* data, int data_len);
void handle_event(const char* data, int data_len);
void report_firmware_version(char* event_time);
void handle_ota_command(const char* data);
void ota_timeout_handler(void *arg);
void setup_ota_timeout_timer(void);
void clear_ota_timeout_timer(void);
static esp_mqtt_client_handle_t esp_mqtt_client_get_handle(void);
static void handle_version_query_manual(const char* data, int data_len);
static void find_field_manually(cJSON *root, const char* field_name);
static void print_json_structure_recursive(cJSON *item, int depth);
void mqtt_main(void);
/** 停止并销毁 MQTT 客户端,释放 TLS/TCP 任务与缓冲;下次 mqtt_main() 会重新创建 */
void mqtt_client_shutdown(void);
esp_err_t subscribe_ota();
static cJSON* create_data(char* service_id, char* event_type);
esp_err_t publish_json_download(char* filename);
bool Get_MQTT_FLAG(void);
extern bool mqtt_connected;
static char current_request_id[64] = {0};
static esp_timer_handle_t ota_timeout_timer;
static bool ota_command_received = false;
static esp_mqtt_client_handle_t client = NULL;
static esp_mqtt_client_handle_t client_http = NULL;
static char time_event[64] = {0};
extern char object_device_id[70];
static char version[64] = {0};
extern char custom_info[32];
void trigger_ota_upgrade(char* url);
void check_ota_progress(void);
void transmit_ota_success(void);
#define ota_upgrade 1
#define ota_flash_upgrade 2
static int ota_type = 0;
void get_object_device_id(char *product_id);
#endif // MQTT_H
+28
View File
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdint.h>
class AudioSourceFile {
public:
FILE* fp;
AudioSourceFile(const char* path) {
fp = fopen(path, "rb");
}
int read(uint8_t* buf, int len) {
if (!fp) return 0;
return fread(buf, 1, len, fp);
}
void seek(int offset) {
if (fp) fseek(fp, offset, SEEK_SET);
}
bool isOpen() {
return fp != nullptr;
}
~AudioSourceFile() {
if (fp) fclose(fp);
}
};
+29
View File
@@ -0,0 +1,29 @@
#include "micro_decoder/decoder_source.h"
#include "micro_decoder/types.h"
#include <stdio.h>
static FILE* g_fp = nullptr;
/*
extern "C" {
void md_init(const char* path)
{
g_fp = fopen(path, "rb");
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp);
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp);
g_fp = nullptr;
}
}
}
*/
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
//void md_init(const char* path);
//int md_read(uint8_t* buf, int len);
//void md_deinit();
#ifdef __cplusplus
}
#endif
+132
View File
@@ -0,0 +1,132 @@
#include "ns4168.h"
#include <stdint.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_log.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "ns4168";
static void ns4168_white_noise_task(void *arg) //白噪音测试
{
uint8_t buffer[256];
size_t bytes_written = 0;
while (1) {
for (int i = 0; i < (int)sizeof(buffer); i += 2) {
uint16_t sample = (uint16_t)(esp_random() & 0xFFFF);
buffer[i] = (uint8_t)(sample & 0xFF);
buffer[i + 1] = (uint8_t)((sample >> 8) & 0xFF);
}
esp_err_t ret = i2s_write(NS4168_I2S_PORT, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_write failed: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/**
* @brief 初始化 NS4168 的 CTRL 引脚并将其拉高(选择右声道,且退出关断模式)
*/
void ns4168_ctrl_init(void)
{
// 1. 配置 GPIO 参数结构体
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << NS4168_CTRL_GPIO), // 选中 GPIO33
.mode = GPIO_MODE_OUTPUT, // 设置为推挽输出
.intr_type = GPIO_INTR_DISABLE, // 禁止中断
.pull_up_en = GPIO_PULLUP_DISABLE, // 关闭内部上拉(不需要)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 关闭内部下拉
};
// 2. 应用配置
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE("NS4168", "GPIO33 配置失败!");
return;
}
// 3. 输出高电平(3.3V)
gpio_set_level(NS4168_CTRL_GPIO, 1);
ESP_LOGI("NS4168", "NS4168 CTRL 引脚已拉高 (GPIO33),选择右声道");
}
esp_err_t ns4168_i2s_init(void)
{
ns4168_ctrl_init();
esp_err_t ret = ESP_OK;
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
.sample_rate = NS4168_I2S_SAMPLE_RATE,
.bits_per_sample = 16,
.channel_format = NS4168_I2S_CHANNELS,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 16,
.dma_buf_len = 1024,
.tx_desc_auto_clear = true,
.use_apll = false,
.fixed_mclk = 0,
};
i2s_pin_config_t pin_cfg = {
.mck_io_num = -1,
.bck_io_num = NS4168_I2S_BCK_PIN,
.ws_io_num = NS4168_I2S_WS_PIN,
.data_out_num = NS4168_I2S_DOUT_PIN,
.data_in_num = I2S_PIN_NO_CHANGE,
};
ret = i2s_driver_install(NS4168_I2S_PORT, &i2s_cfg, 0, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_driver_install failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_set_pin(NS4168_I2S_PORT, &pin_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_set_pin failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_zero_dma_buffer(NS4168_I2S_PORT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_zero_dma_buffer failed: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "NS4168 I2S init OK");
return ESP_OK;
}
esp_err_t ns4168_start_white_noise_test(void)
{
esp_err_t ret = ns4168_i2s_init();
if (ret != ESP_OK) {
return ret;
}
BaseType_t task_ret = xTaskCreatePinnedToCore(ns4168_white_noise_task, "ns4168_noise", 4096, NULL, 5, NULL, 1);
if (task_ret != pdPASS) {
ESP_LOGE(TAG, "create white noise task failed");
return ESP_FAIL;
}
return ESP_OK;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __NS4168_H__
#define __NS4168_H__
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 先用宏定义占位引脚,后续再改成实际板子上的 GPIO */
#define NS4168_I2S_PORT I2S_NUM_1
#define NS4168_CTRL_GPIO GPIO_NUM_33
#define NS4168_I2S_BCK_PIN GPIO_NUM_31
#define NS4168_I2S_WS_PIN GPIO_NUM_32
#define NS4168_I2S_DOUT_PIN GPIO_NUM_30
#define NS4168_I2S_SAMPLE_RATE 24000
#define NS4168_I2S_BITS I2S_BITS_PER_SAMPLE_16BIT
#define NS4168_I2S_CHANNELS I2S_CHANNEL_FMT_ONLY_RIGHT
esp_err_t ns4168_i2s_init(void);
esp_err_t ns4168_start_white_noise_test(void);
#ifdef __cplusplus
}
#endif
#endif /* __NS4168_H__ */
+58
View File
@@ -0,0 +1,58 @@
#include "ogg_demux.h"
static uint32_t le32(const uint8_t* p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page)
{
if (buf_len < 27)
return -1;
/* 校验 "OggS" */
if (buf[0] != 'O' || buf[1] != 'g' || buf[2] != 'g' || buf[3] != 'S')
return -2;
/* 拷贝页头 */
for (int i = 0; i < 27; ++i)
page->header[i] = buf[i];
page->seg_count = buf[26];
if (buf_len < 27 + page->seg_count)
return -3;
/* 读取 segment table */
for (int i = 0; i < page->seg_count; ++i)
page->seg_table[i] = buf[27 + i];
/* 计算 body 大小 */
uint32_t body_size = 0;
for (int i = 0; i < page->seg_count; ++i)
body_size += page->seg_table[i];
page->body_size = body_size;
if (buf_len < 27 + page->seg_count + body_size)
return -4;
return (int)body_size;
}
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size)
{
/* 极简版:假设一个页里只有一个完整 packet(常见于 OpusHead / OpusTags) */
uint32_t total = page->body_size;
if (total > (uint32_t)packet_buf_size)
return -1;
for (uint32_t i = 0; i < total; ++i)
packet[i] = body[i];
return (int)total;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef OGG_DEMUX_H
#define OGG_DEMUX_H
#include <stdint.h>
typedef struct {
uint8_t header[27]; /* Ogg 页头 */
uint8_t seg_count;
uint8_t seg_table[255];
uint32_t body_size;
} ogg_page_t;
/* 从数据缓冲中解析一页 Ogg,返回页体大小,<0 表示错误 */
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page);
/* 根据 seg_table 把页体里的数据拼成一个 packet
返回 packet 长度,<0 表示错误 */
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size);
#endif
+379
View File
@@ -0,0 +1,379 @@
#include <stdint.h>
#include "esp_log.h"
#include "NS4168/micro_decoder_wrapper.h"
#include "NS4168/wav_demux.h"
#include "NS4168/ns4168.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
static FILE* g_fp = NULL;
void md_init(const char* path)
{
g_fp = fopen(path, "rb"); // 标准 C
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp); // 标准 C
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp); // 标准 C
g_fp = NULL; // 标准 C(替换 nullptr)
}
}
void play_wav(void)
{
ns4168_i2s_init();
uint8_t header[70];
size_t written = 0;
int ret;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
/**
* @param volume 音量系数,0.0=静音,1.0=原始音量,推荐 0.0~1.0
*/
void play_wav2(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 仅当音量不是 1.0 时才缩放(优化性能)
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 假定为 16-bit PCM
for (int i = 0; i < num_samples; i++) {
int32_t val = (int32_t)(samples[i] * volume);
// 饱和限幅,防止溢出破音
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav3(float volume)
{
// 1. 打开文件
md_init("/extflash/ring2.wav");
// 2. 读取头部(沿用 70 字节,兼容你的原逻辑)
uint8_t header[70];
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放,音量=%.2f", volume);
// 3. 将浮点音量转换为 Q15 定点数 (范围 0 ~ 32768)
int32_t vol_fixed = (int32_t)(volume * 32768.0f + 0.5f);
if (vol_fixed > 32768) vol_fixed = 32768;
if (vol_fixed < 0) vol_fixed = 0;
size_t written = 0;
uint8_t buf[1024];
int n;
// 4. 循环读取音频数据并处理
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 确保处理偶数个字节,防止破坏样本对齐
int bytes_to_process = (n / 2) * 2;
int num_samples = bytes_to_process / 2;
int16_t *samples = (int16_t *)buf;
// 音量接近静音时直接填零(避免处理微小信号产生的噪声)
if (vol_fixed < 32) {
memset(buf, 0, bytes_to_process);
}
else if (vol_fixed != 32768) { // 不等于 1.0 时才缩放
for (int i = 0; i < num_samples; i++) {
// 整数定点缩放 + 四舍五入
int32_t val = ((int32_t)samples[i] * vol_fixed + 16384) >> 15;
// 饱和限幅(实际不会溢出,但保留保护)
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
// 发送对齐后的数据(丢弃奇数尾字节)
i2s_write(NS4168_I2S_PORT, buf, bytes_to_process, &written, portMAX_DELAY);
}
// 5. 关闭文件
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 浮点缩放
float scaled = (float)samples[i] * volume;
// 四舍五入(关键)
int32_t val = lrintf(scaled);
// 饱和限幅
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav5(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 将浮点音量转换为 8-bit 定点(0~256)
int32_t vol_fixed = (int32_t)(volume * 256.0f);
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 原始 16-bit PCM
int32_t v = samples[i];
// 定点缩放:扩大到 24-bit 精度再缩回
v = (v * vol_fixed) >> 8;
// 饱和限幅
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
samples[i] = (int16_t)v;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav_p4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 如果音量是 1.0,直接原样播放,完全无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// 简单 dither 强度(可以微调)
const float dither_amp = 1.0f / 512.0f; // 很小的抖动
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 加一点点 dither,打散量化误差
// 简单线性同余随机数(不依赖 rand())
static uint32_t rnd = 0x12345678;
rnd = rnd * 1664525u + 1013904223u;
float noise = ((int32_t)(rnd >> 16) - 32768) * (dither_amp / 32768.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0 ? 1.0f : -1.0f));
// 饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
#include <stdbool.h>
#include "driver/i2s.h"
// 简单的 32-bit 伪随机数,用于 dither
static inline uint32_t prng32(void)
{
static uint32_t s = 0x12345678u;
s = s * 1664525u + 1013904223u;
return s;
}
void play_wav_ring_with_volume(float volume)
{
const char *TAG = "WAV_RING";
uint8_t header[70];
size_t written = 0;
// 初始化并读取 WAV 头
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX(TAG, header, sizeof(header));
ESP_LOGI(TAG, "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 音量为 1.0 时完全绕过处理,保证绝对无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// dither 强度(非常小,人耳几乎感知不到)
const float dither_amp = 1.0f / 4096.0f; // 可微调
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 三角 dither(TDPF):(rand1 - rand2) * scale
uint32_t r1 = prng32();
uint32_t r2 = prng32();
float noise = ((int32_t)(r1 & 0xFFFF) - (int32_t)(r2 & 0xFFFF))
* (dither_amp / 65536.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0.0f ? 1.0f : -1.0f));
// 轻微 soft-clip,避免极端削波毛刺
if (v_rounded > 32767.0f) {
float over = v_rounded - 32767.0f;
v_rounded = 32767.0f + over * 0.01f; // 把硬削波变成软削波
} else if (v_rounded < -32768.0f) {
float under = v_rounded + 32768.0f;
v_rounded = -32768.0f + under * 0.01f;
}
// 最终饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
// 写入 I2S
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI(TAG, "WAV播放结束");
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef WAV_DEMUX_H
#define WAV_DEMUX_H
#include <stdint.h>
void play_wav(void);
#endif
+43
View File
@@ -0,0 +1,43 @@
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs.h"
#include "esp_log.h"
#include <string.h>
static const char *TAG = "emoji_information";
//NVS 命名空间
#define NVS_NAMESPACE "emoji_config"
//版本号的键名
#define VERSION_KEY "emoji_version"
#define VERSION_BUF_SIZE 32
esp_err_t nvs_emoji_init(void) // nvs 初始化
{
esp_err_t err = nvs_flash_init();
if(err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_LOGI(TAG,"NVS paratition need erase , reinitializing ...");
err = nvs_flash_erase();
if(err != ESP_OK)
{
ESP_LOGI(TAG,"NVS flash erase failed : %s ", esp_err_to_name(err));
return err;
}
err = nvs_flash_init();
}
if(err != ESP_OK)
{
ESP_LOGI(TAG,"NVS initialization failed : %s",esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG,"NVS initialized successfully");
return ESP_OK;
}
+5
View File
@@ -0,0 +1,5 @@
#ifndef __NVS_FLASH_H__
#define __NVS_FLASH_H__
#endif
+842
View File
@@ -0,0 +1,842 @@
// version 1.0.2
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"
#include "esp_attr.h"
#include "hal/cache_ll.h"
#include "hal/cache_hal.h"
#include "esp_private/cache_utils.h"
#include "esp_task_wdt.h" // 看门狗函数
#include "esp_heap_caps.h"
#include <unistd.h> // 添加这行
#include "config.h"
#include "BLE/ble.h"
#include "ota_upgrade.h"
#include "event.h"
extern void ota_main_notify_recover_pending_after_fail(void);
StaticTask_t ota_task_tcb;
StackType_t *ota_task_stack;
extern esp_mqtt_client_handle_t client_http;
extern const char *base_path;
extern char time_event[64];
extern char version[64];
extern char object_device_id[70];
extern char custom_info[32];
extern wl_handle_t s_wl_handle;
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition);
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size);
static const char *TAG_OTA = "OTA";
static ota_upgrade_context_t ota_ctx = {0};
int g_progress = 0;
/** OTA 任务结束时释放,供 ota_upgrade_cancel() 等待,避免无限阻塞 */
static SemaphoreHandle_t s_ota_done_sem = NULL;
// 内部函数声明
static void ota_task(void *pvParameter);
static void notify_status(const char *status, int progress, const char *message);
static void http_cleanup(esp_http_client_handle_t client);
esp_err_t ota_upgrade_init(void) // 初始化OTA升级
{
if (ota_ctx.mutex == NULL)
{
ota_ctx.mutex = xSemaphoreCreateMutex(); // 创建互斥锁
if (ota_ctx.mutex == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create mutex");
return ESP_FAIL;
}
}
if (s_ota_done_sem == NULL)
{
s_ota_done_sem = xSemaphoreCreateBinary();
if (s_ota_done_sem == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA done semaphore");
return ESP_FAIL;
}
}
ota_ctx.progress = 0;
ota_ctx.is_running = false;
ota_ctx.cancel_requested = false;
ota_ctx.status_callback = NULL;
return ESP_OK;
}
void ota_upgrade_set_callback(ota_status_callback_t callback) //
{
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.status_callback = callback;
xSemaphoreGive(ota_ctx.mutex);
}
}
esp_err_t ota_upgrade_start(const char *firmware_url) // OTA 升级开始
{
if (firmware_url == NULL)
{
ESP_LOGE(TAG_OTA, "Firmware URL is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGW(TAG_OTA, "OTA upgrade is already running");
return ESP_ERR_INVALID_STATE;
}
// 复制URL到动态内存
char *url_copy = strdup(firmware_url);
if (url_copy == NULL)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGE(TAG_OTA, "Failed to allocate memory for URL");
return ESP_ERR_NO_MEM;
}
ota_ctx.is_running = true;
ota_ctx.progress = 0;
ota_ctx.cancel_requested = false;
xSemaphoreGive(ota_ctx.mutex); // 发送互斥信号量
/* 消费可能存在的上一次任务结束信号,避免 cancel 误唤醒 */
if (s_ota_done_sem != NULL)
{
xSemaphoreTake(s_ota_done_sem, 0);
}
ESP_LOGI(TAG_OTA, "Allocating OTA task stack: %d words (%d bytes) from SPIRAM",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
ESP_LOGI(TAG_OTA, "Note: esp_ota_end() will run on a separate internal-RAM stack");
ota_task_stack = heap_caps_malloc(OTA_TASK_STACK_SIZE * sizeof(StackType_t), MALLOC_CAP_SPIRAM);
if (ota_task_stack == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to allocate stack for OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
ota_ctx.task_handle = xTaskCreateStaticPinnedToCore(
ota_task,
"ota_task",
OTA_TASK_STACK_SIZE,
url_copy,
configMAX_PRIORITIES - 1, // 高优先级
ota_task_stack,
&ota_task_tcb,
1 // 固定核心
);
if (ota_ctx.task_handle == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t ota_upgrade_start_blocking(const char *firmware_url) // OTA启动阻塞
{
esp_err_t ret = ESP_FAIL;
if (firmware_url == NULL)
{
return ESP_ERR_INVALID_ARG;
}
// 直接调用OTA任务函数(阻塞)
ota_task((void *)firmware_url);
// 如果OTA成功,这里不会返回(设备会重启)
// 如果失败,会返回到这里
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ret = ota_ctx.is_running ? ESP_FAIL : ESP_OK;
xSemaphoreGive(ota_ctx.mutex);
}
return ret;
}
int ota_upgrade_get_progress(void) // 获取OTA升级进度
{
int progress = -1;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
progress = ota_ctx.progress;
xSemaphoreGive(ota_ctx.mutex);
}
return progress;
}
bool ota_upgrade_is_running(void)
{
bool is_running = false;
/* 未调用 ota_upgrade_init() 时 mutex 可能为 NULL,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
return false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY)) // 如果拿到了信号量
{
is_running = ota_ctx.is_running;
xSemaphoreGive(ota_ctx.mutex);
}
return is_running;
}
esp_err_t ota_upgrade_cancel(void) // OTA取消升级
{
/* 若从未调用 ota_upgrade_init(),mutex 为 NULL,直接返回,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
{
ESP_LOGW(TAG_OTA, "OTA not initialized, nothing to cancel");
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (!ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
return ESP_ERR_INVALID_STATE;
}
ota_ctx.cancel_requested = true;
xSemaphoreGive(ota_ctx.mutex);
/* 等待 OTA 任务结束(任务退出时会 Give 此信号量),避免无限阻塞 */
if (s_ota_done_sem != NULL && ota_ctx.task_handle != NULL)
{
xSemaphoreTake(s_ota_done_sem, portMAX_DELAY);
}
return ESP_OK;
}
return ESP_FAIL;
}
/* ─── Generic helper: run a function on an internal-RAM stack ───
*
* Functions like esp_ota_end() and esp_ota_set_boot_partition() call
* esp_image_verify() → spi_flash_mmap() which disables the cache,
* making SPIRAM inaccessible. If the calling task's stack lives in
* SPIRAM the assertion esp_task_stack_is_sane_cache_disabled() fires.
* This helper spawns a short-lived task whose stack is explicitly
* allocated from internal SRAM.
*/
#define INTERNAL_STACK_SIZE (1024 * 4) /* 4096 words = 16 KB */
typedef esp_err_t (*internal_fn_t)(void *arg);
typedef struct {
internal_fn_t fn;
void *arg;
esp_err_t result;
SemaphoreHandle_t done;
} internal_op_t;
static void internal_stack_task(void *param)
{
internal_op_t *op = (internal_op_t *)param;
op->result = op->fn(op->arg);
xSemaphoreGive(op->done);
vTaskSuspend(NULL);
}
static esp_err_t run_on_internal_stack(internal_fn_t fn, void *arg, const char *label)
{
ESP_LOGI(TAG_OTA, "[%s] Preparing internal-RAM task (free: %u bytes)",
label, (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
internal_op_t op = {
.fn = fn,
.arg = arg,
.result = ESP_FAIL,
.done = xSemaphoreCreateBinary(),
};
if (op.done == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create semaphore", label);
return ESP_FAIL;
}
StackType_t *stk = heap_caps_malloc(INTERNAL_STACK_SIZE * sizeof(StackType_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
StaticTask_t *tcb = heap_caps_malloc(sizeof(StaticTask_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (stk == NULL || tcb == NULL) {
ESP_LOGE(TAG_OTA, "[%s] No internal RAM (need %u bytes)", label,
(unsigned)(INTERNAL_STACK_SIZE * sizeof(StackType_t) + sizeof(StaticTask_t)));
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_ERR_NO_MEM;
}
TaskHandle_t th = xTaskCreateStaticPinnedToCore(
internal_stack_task, "int_op",
INTERNAL_STACK_SIZE, &op,
configMAX_PRIORITIES - 1,
stk, tcb, 1);
if (th == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create task", label);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_FAIL;
}
ESP_LOGI(TAG_OTA, "[%s] Task created, waiting...", label);
xSemaphoreTake(op.done, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(th);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
ESP_LOGI(TAG_OTA, "[%s] Done, result: %s (0x%x)", label,
esp_err_to_name(op.result), (unsigned)op.result);
return op.result;
}
static esp_err_t wrap_ota_end(void *arg)
{
return esp_ota_end((esp_ota_handle_t)(uintptr_t)arg);
}
static esp_err_t wrap_set_boot_partition(void *arg)
{
return esp_ota_set_boot_partition((const esp_partition_t *)arg);
}
/* ─── Main OTA task ─── */
static void ota_task(void *pvParameter)
{
char *firmware_url = (char *)pvParameter;
ESP_LOGI(TAG_OTA, "===== OTA task started =====");
ESP_LOGI(TAG_OTA, " URL: %s", firmware_url);
ESP_LOGI(TAG_OTA, " Free internal RAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, " Free SPIRAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
ESP_LOGI(TAG_OTA, " Task stack in SPIRAM, size: %d words (%d bytes)",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
esp_http_client_config_t config = {
.url = firmware_url,
.timeout_ms = 300000, // 5分钟超时
.buffer_size = 10240, // 增大缓冲区
.buffer_size_tx = 10240,
.keep_alive_enable = true, // 启用长连接
.keep_alive_idle = 30, // 30秒空闲时间
.keep_alive_interval = 10, // 10秒保活间隔
.keep_alive_count = 5, // 5次保活探测
.disable_auto_redirect = false, // 允许重定向
.max_redirection_count = 3, // 最多3次重定向
.auth_type = HTTP_AUTH_TYPE_NONE,
.user_data = NULL,
.is_async = false,
.use_global_ca_store = false,
.skip_cert_common_name_check = true,
.crt_bundle_attach = NULL,
.cert_pem = NULL, // (const char *)server_cert_pem_start,
};
esp_err_t err = ESP_FAIL;
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
esp_http_client_handle_t client = NULL;
int binary_file_length = 0;
int content_length = 0;
bool image_header_was_checked = false;
char ota_write_data[BUFFSIZE + 1] = {0};
static int s_last_sent_progress = -1;
s_last_sent_progress = -1; /* 每次 OTA 开始时重置,确保进度每增加 1% 只发一次、不重复发相同值 */
notify_status("starting", 0, "Starting OTA upgrade");
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "text/plain");
// 优化TCP连接参数
// esp_http_client_set_tcp_nodelay(client, true); // 禁用Nagle算法,减少延迟
// esp_http_client_set_redirection(client, true); // 启用重定向
if (client == NULL)
{
notify_status("error", 0, "Failed to initialize HTTP client");
goto cleanup;
}
// 打开HTTP连接
err = esp_http_client_open(client, 0);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to open HTTP connection");
goto cleanup;
}
content_length = esp_http_client_fetch_headers(client);
if (content_length <= 0)
{
err = ESP_FAIL;
notify_status("error", 0, "Invalid content length");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "HTTP connected, content_length=%d bytes", content_length);
notify_status("downloading", 0, "Connected to server, starting download");
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL)
{
err = ESP_FAIL;
notify_status("error", 0, "No OTA update partition found");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "OTA partition: label=%s, addr=0x%lx, size=%lu bytes",
update_partition->label, update_partition->address, update_partition->size);
// 处理接收数据
while (1)
{
// 检查取消请求
if (xSemaphoreTake(ota_ctx.mutex, 0))
{
if (ota_ctx.cancel_requested)
{
xSemaphoreGive(ota_ctx.mutex);
notify_status("cancelled", ota_ctx.progress, "OTA upgrade cancelled");
err = ESP_FAIL;
goto cleanup;
}
xSemaphoreGive(ota_ctx.mutex);
}
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
if (data_read < 0)
{
err = ESP_FAIL;
ESP_LOGE(TAG_OTA, "[error] %d%%: Network read error (connection lost)", ota_ctx.progress);
notify_status("error", ota_ctx.progress, "Network read error");
goto cleanup;
}
else if (data_read > 0)
{
if (!image_header_was_checked)
{
esp_app_desc_t new_app_info;
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t))
{
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
notify_status("downloading", 5, "Firmware header verified");
err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to begin OTA update");
goto cleanup;
}
image_header_was_checked = true;
}
}
// 写入OTA数据
err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read);
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "Failed to write OTA data");
goto cleanup;
}
binary_file_length += data_read;
// 更新进度
if (content_length > 0)
{
// int progress = ota_upgrade_get_progress();
int progress = (binary_file_length * 95) / content_length; // 留5%给最后的验证和设置
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.progress = progress;
xSemaphoreGive(ota_ctx.mutex);
}
g_progress = progress;
ESP_LOGI(TAG_OTA, "OTA progress %d%%", progress);
/* 进度每增加 1% 仅通过 BLE 协议帧 1.11_B 发送,不使用 JSON */
if (progress > s_last_sent_progress)
{
s_last_sent_progress = progress;
ble_send_ota_progress((uint8_t)progress);
}
notify_status("downloading", progress, "Downloading firmware");
}
}
else if (data_read == 0)
{
if (esp_http_client_is_complete_data_received(client))
{
break;
}
}
}
// 验证数据完整性
if (!esp_http_client_is_complete_data_received(client))
{
err = ESP_FAIL;
notify_status("error", ota_ctx.progress, "Incomplete data received");
goto cleanup;
}
notify_status("verifying", 95, "Download complete, verifying firmware");
ESP_LOGI(TAG_OTA, "Download finished: %d bytes received (content_length=%d)",
binary_file_length, content_length);
ESP_LOGI(TAG_OTA, "Free internal RAM before verify: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, "Free SPIRAM before verify : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
err = run_on_internal_stack(wrap_ota_end, (void *)(uintptr_t)update_handle, "ota_end");
if (err != ESP_OK)
{
ESP_LOGE(TAG_OTA, "esp_ota_end failed: %s (0x%x)", esp_err_to_name(err), (unsigned)err);
notify_status("error", 100, "Firmware verification failed");
goto cleanup;
}
update_handle = 0;
notify_status("finalizing", 98, "Setting boot partition");
// 设置启动分区(storage 区已废弃,不再提取到 storage,统一走 set_boot_partition)
if (0 && strstr(custom_info, "flash") != NULL) /* 原:提取到 storage 分区,已禁用 */
{
err = extract_running_app_to_storage("/extflash/flash.bin", update_partition);
}
else
{
err = run_on_internal_stack(wrap_set_boot_partition,
(void *)update_partition, "set_boot");
}
if (err != ESP_OK)
{
notify_status("error", 100, "Failed to set boot partition");
goto cleanup;
}
notify_status("complete", 100, "OTA upgrade complete, restarting...");
ble_send_ota_progress(100);
vTaskDelay(pdMS_TO_TICKS(500));
ble_send_ota_result_request(1); /* 1.11_A: 通知蓝牙主机 OTA 升级成功 */
vTaskDelay(pdMS_TO_TICKS(500)); /* 留时间给 BLE 发出结果帧 */
transmit_ota_success();
//send_ota_result_frame(0x01);
set_system_event(OTA_UPDATE_COMPLETED_BIT);
/* main_task 可能不存在,直接在此重启,不依赖事件队列 */
ESP_LOGI(TAG_OTA, "OTA 升级完成,将重启设备...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
cleanup:
ESP_LOGI(TAG_OTA, "===== OTA cleanup: err=%s (0x%x) =====",
esp_err_to_name(err), (unsigned)err);
ESP_LOGI(TAG_OTA, " Free internal RAM: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
/* 1. 安全终止 OTA 写入(仅失败时) */
if (err != ESP_OK && update_handle != 0)
{
ESP_LOGW(TAG_OTA, "Aborting OTA update handle");
esp_ota_abort(update_handle);
}
/* 2. 关闭 HTTP 连接,释放资源 */
if (client != NULL)
{
http_cleanup(client);
}
/* 3. 立即清除运行状态,使系统可再次发起 OTA / 正常业务 */
bool ota_cancelled_by_user = false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_cancelled_by_user = ota_ctx.cancel_requested;
ota_ctx.is_running = false;
ota_ctx.task_handle = NULL;
xSemaphoreGive(ota_ctx.mutex);
}
free(firmware_url);
/* 4. 失败时:上报 OTA 结果并安全退出,不卡死流程 */
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "OTA upgrade failed");
if (ble_is_connected())
{
ble_send_ota_result_request(0); /* 1.11_A: 通知蓝牙主机 OTA 升级失败 */
vTaskDelay(pdMS_TO_TICKS(300)); /* 留时间给 BLE 发出结果帧 */
}
else
{
ESP_LOGW(TAG_OTA, "BLE 已断开,跳过 OTA 失败结果上报");
}
}
/* 5. 通知可能正在 ota_upgrade_cancel() 中等待的调用者,然后退出任务 */
if (s_ota_done_sem != NULL)
{
xSemaphoreGive(s_ota_done_sem);
}
/* 失败且非用户取消:延后恢复 UI,等 WiFi 重连结束(由 main 根据 WiFi 状态再 ota_restore) */
if (err != ESP_OK && !ota_cancelled_by_user)
{
ota_main_notify_recover_pending_after_fail();
}
vTaskDelete(NULL);
}
static void notify_status(const char *status, int progress, const char *message)
{
ESP_LOGI(TAG_OTA, "[%s] %d%%: %s", status, progress, message);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.status_callback != NULL)
{
ota_ctx.status_callback(status, progress, message);
}
xSemaphoreGive(ota_ctx.mutex);
}
}
static void http_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
// 在需要触发OTA升级的地方调用(例如按钮按下、定时器到期等)
void trigger_ota_upgrade(char *url)
{
const char *firmware_url = url;
ESP_LOGI(TAG_OTA, "[DEBUG] trigger_ota_upgrade url=%s", url ? url : "NULL");
if (!ota_upgrade_is_running())
{
esp_err_t ret = ota_upgrade_start(firmware_url);
ESP_LOGI(TAG_OTA, "[DEBUG] ota_upgrade_start 返回: %s", ret == ESP_OK ? "OK" : esp_err_to_name(ret));
if (ret == ESP_OK)
{
printf("OTA upgrade started successfully\n");
}
else
{
printf("Failed to start OTA upgrade: %d\n", ret);
}
}
else
{
ESP_LOGW(TAG_OTA, "[DEBUG] OTA 已在运行中,忽略");
printf("OTA upgrade is already running\n");
}
}
// 取消OTA升级
void cancel_ota_upgrade(void)
{
if (ota_upgrade_cancel() == ESP_OK)
{
printf("OTA upgrade cancelled\n");
}
else
{
printf("Failed to cancel OTA upgrade\n");
}
}
// 获取升级进度
void check_ota_progress(void)
{
int progress = ota_upgrade_get_progress();
if (progress >= 0)
{
printf("Current OTA progress: %d%%\n", progress);
}
else
{
printf("No OTA upgrade in progress\n");
}
}
// 提取当前运行分区的bin文件到storage分区
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition)
{
printf("=== 提取运行中的应用到storage分区 ===\n");
// 2. 查找storage分区
const esp_partition_t *storage_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, 129, "storage");
if (storage_partition == NULL)
{
printf("❌ 找不到storage分区\n");
return ESP_FAIL;
}
printf("目标storage分区: 地址: 0x%ld, 大小: %ld\n",
storage_partition->address, storage_partition->size);
// 3. 检查storage分区空间是否足够
if (storage_partition->size < running_partition->size)
{
printf("❌ storage分区空间不足,需要%ld字节,只有%ld字节\n",
running_partition->size, storage_partition->size);
return ESP_ERR_INVALID_SIZE;
}
// 4. 分配读取缓冲区
size_t buffer_size = 4096; // 4KB缓冲区
uint8_t *buffer = malloc(buffer_size);
if (buffer == NULL)
{
printf("❌ 内存分配失败\n");
return ESP_ERR_NO_MEM;
}
// 5. 读取应用分区并写入storage分区
size_t total_read = 0;
esp_err_t err = ESP_OK;
printf("开始提取应用bin文件...\n");
while (total_read < running_partition->size)
{
size_t to_read = (running_partition->size - total_read) < buffer_size ? (running_partition->size - total_read) : buffer_size;
// 从应用分区读取
err = esp_partition_read(running_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 读取应用分区失败: %s\n", esp_err_to_name(err));
break;
}
// 写入storage分区
err = esp_partition_write(storage_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 写入storage分区失败: %s\n", esp_err_to_name(err));
break;
}
total_read += to_read;
// 显示进度
int progress = (total_read * 100) / running_partition->size;
if (progress % 10 == 0)
{ // 每10%显示一次进度
printf("提取进度: %d%%, 已提取: %d/%ld 字节\n",
progress, total_read, running_partition->size);
}
}
free(buffer);
if (err == ESP_OK)
{
printf("✅ 应用bin文件提取完成,总共提取 %d 字节\n", total_read);
// 保存提取信息到NVS
save_extraction_info(filename, running_partition->size, total_read);
}
else
{
printf("❌ 提取失败\n");
}
return err;
}
// 保存提取信息到NVS
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size)
{
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("extraction", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK)
return err;
extraction_info_t info = {
.original_size = original_size,
.extracted_size = extracted_size,
.timestamp = (uint32_t)time(NULL),
.compressed = false,
.verified = false};
strncpy(info.filename, filename, sizeof(info.filename) - 1);
// 计算CRC(简化示例)
info.crc = original_size ^ extracted_size;
err = nvs_set_blob(nvs_handle, filename, &info, sizeof(info));
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
if (err == ESP_OK)
{
printf("✅ 提取信息已保存: %s\n", filename);
}
return err;
}
+109
View File
@@ -0,0 +1,109 @@
#ifndef OTA_UPGRADE_H
#define OTA_UPGRADE_H
#include "esp_err.h"
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"
#define BUFFSIZE (1024*4)
#define OTA_TASK_STACK_SIZE 1024*24//1024*24
#define OTA_TASK_PRIORITY 5
/**
* @brief OTA升级状态回调函数类型
* @param status 升级状态
* @param progress 升级进度(0-100)
* @param message 状态信息
*/
typedef void (*ota_status_callback_t)(const char* status, int progress, const char* message);
// 提取信息管理
typedef struct
{
char filename[64];
uint32_t original_size;
uint32_t extracted_size;
uint32_t timestamp;
uint32_t crc;
bool compressed;
bool verified;
} extraction_info_t;
// OTA升级状态结构体
typedef struct
{
TaskHandle_t task_handle;
ota_status_callback_t status_callback;
SemaphoreHandle_t mutex; //互斥信号量
bool is_running;
int progress;
bool cancel_requested;
} ota_upgrade_context_t;
esp_err_t http_json_download_flash(char* filename, const char* firmware_url);
void trigger_ota_upgrade(char* url);
void check_ota_progress(void);
/**
* @brief 初始化OTA升级模块
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t ota_upgrade_init(void);
/**
* @brief 设置OTA升级状态回调函数
* @param callback 回调函数指针
*/
void ota_upgrade_set_callback(ota_status_callback_t callback);
/**
* @brief 开始OTA升级(非阻塞方式,在新任务中运行)
* @param firmware_url 固件下载URL
* @return esp_err_t ESP_OK表示任务创建成功
*/
esp_err_t ota_upgrade_start(const char* firmware_url);
/**
* @brief 开始OTA升级(阻塞方式,在当前任务中运行)
* @param firmware_url 固件下载URL
* @return esp_err_t 升级结果
*/
esp_err_t ota_upgrade_start_blocking(const char* firmware_url);
/**
* @brief 获取当前升级进度
* @return int 进度百分比(0-100),-1表示未在升级
*/
int ota_upgrade_get_progress(void);
/**
* @brief 检查是否正在升级
* @return true 正在升级
* @return false 未在升级
*/
bool ota_upgrade_is_running(void);
/**
* @brief 取消OTA升级
* @return esp_err_t ESP_OK表示取消成功
*/
esp_err_t ota_upgrade_cancel(void);
#endif // OTA_UPGRADE_H
+763
View File
@@ -0,0 +1,763 @@
#include "pet_init.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <sys/time.h>
#include <stdio.h>
#include "esp_netif_sntp.h"
#include "freertos/queue.h"
#include "esp_random.h"
#define TAG "PET"
static TaskHandle_t pet_task_handle = NULL;
static void pet_time_sync_task(void *arg);
static void pet_user_write_task(void *arg);
QueueHandle_t user_interact_Queue = NULL;
bool is_ntp_initalized = false;
static void feed_ready_handler(TimerHandle_t xTimer);
TimerHandle_t feed_timer=NULL;
static void travel_ready_handler(TimerHandle_t xTimer);
TimerHandle_t travel_timer=NULL;
uint8_t remaining_hunger=0;
uint8_t remaining_cleanliness=0;
uint8_t remaining_mood=0;
static pet_status_t s_pet_status = {
.hunger = 0, //饱腹值
.cleanliness = 0, //清洁度
.mood = 0, //心情
.intimacy = 0, //亲密度
.stage = PET_STAGE_BABY, //成长状态
.vehicle = PET_VEHICLE_WALK, //出行方式
.last_timestamp = 0, //时间
.last_time = {0},
.travel_time =0, //当前旅行所需时间
.is_travelling = false, //是否在旅游
.travel_timestamp = 0, //触发旅游出行的时间(可计算)
.can_travel = false, //能否旅游状态(可计算)
.feed_time = 14400, //喂食所需时间(固定值)
.feed_timestamp = 0, //上一次喂食时间
.is_feed_cd = false, //是否在喂食冷却
};
/* 城市中文名数组 */
const char *city_names[CITY_COUNT] = {
"公园", "街道", "河流", "古镇", "山道", "水库", "聚落", "果园", "惠州", "东莞", "佛山", "中山", "江门", "清远", "肇庆", "河源", "梅州",
"重庆", "西安", "广州", "南京", "三亚", "北京", "长沙", "张家界", "湘西", "凤凰古镇", "上海", "香格里拉", "丽江", "大理", "西藏",
"厦门", "景德镇", "平潭", "青岛", "北海", "万宁", "淄博", "延吉", "柳州", "宜昌", "漠河", "桂林", "杭州", "西塘", "青海",
"西双版纳", "敦煌", "云南",
};
/* 宠物阶段介绍文案 */
static const char baby_introduction[] = "我还小小的呢,先在附近逛逛就很开心啦!";
static const char teen_introduction[] = "我长大啦!现在可以去更远的城市看一看咯!";
static const char adult_introduction[] = "谢谢你一直陪着我!现在,天涯海角我都能去到啦!";
//static const char travel_return[] = "我回来啦!快看我带回来的好东西~";
//static const char hunger[] = "我回来啦!快看我带回来的好东西~";
static const char *pet_stage_introduction[3] = {
baby_introduction,
teen_introduction,
adult_introduction,
};
/* 全局状态标志 */
bool g_pet_need_first_sync = false; //第一次初始化NVS
bool g_pet_ntp_synced = false; //开机第一次同步NTP
bool g_pet_animation_ready = false; //授时完成等待显示
bool go_travelling() {
// 1. 获取一个32位硬件随机数
uint32_t random_num = esp_random();
// 2. 将随机数映射到 0-99 的范围,判断是否小于20
// 由于随机数在32位空间均匀分布[reference:5],取余100即可得到0-99的均匀分布
if (random_num % 100 < 20) {
return true; // 旅行,award = 1
} else {
return false; // 不旅行,award = 0
}
}
void print_date_time(void) {
time_t now;
struct tm timeinfo;
time(&now); // 获取当前时间戳(秒)
localtime_r(&now, &timeinfo); // 转换为本地时间(需先设置时区)
// 提取年、月、日
int year = timeinfo.tm_year + 1900; // tm_year 从 1900 开始计数
int month = timeinfo.tm_mon + 1; // tm_mon 范围 0~11,需加 1
int day = timeinfo.tm_mday; // tm_mday 为当月第几天(1~31)
// 也可以同时获取时、分、秒
int hour = timeinfo.tm_hour;
int minute = timeinfo.tm_min;
int second = timeinfo.tm_sec;
// 打印日期和时间
ESP_LOGI("NTP", "日期: %04d-%02d-%02d", year, month, day);
ESP_LOGI("NTP", "时间: %02d:%02d:%02d", hour, minute, second);
// 或者格式化输出完整时间
char buffer[64];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
ESP_LOGI("NTP", "当前本地时间: %s", buffer);
}
bool obtain_time(void)
{
ESP_LOGI("NTP", "Initializing SNTP");
if(!is_ntp_initalized){
// 1. 设置SNTP服务器
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
// 国内用户可更换为: "cn.pool.ntp.org" 或 "ntp.aliyun.com"
// 2. 初始化并启动SNTP服务
esp_netif_sntp_init(&config);
is_ntp_initalized = true;
}
// 3. 等待时间同步完成 (超时10秒)
if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) == ESP_OK) {
ESP_LOGI("NTP", "Time synchronized successfully");
} else {
ESP_LOGE("NTP", "Failed to synchronize time");
return false; // 同步失败,返回 false
}
// 4. 设置时区 (中国时区 UTC+8)
setenv("TZ", "CST-8", 1);
tzset();
// 5. 获取并打印当前时间
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI("NTP", "Current time: %s", strftime_buf);
return true; // 同步成功,返回 true
}
static uint8_t clamp_u8(uint8_t value, uint8_t min, uint8_t max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
static uint16_t clamp_u16(uint16_t value, uint16_t min, uint16_t max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
static void pet_save_status_to_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "打开 NVS pet_data 失败: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(handle, "hunger", s_pet_status.hunger);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 hunger 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 cleanliness 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "mood", s_pet_status.mood);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 mood 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u16(handle, "intimacy", s_pet_status.intimacy);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 intimacy 失败: %s", esp_err_to_name(err));
}
/* 保存最后时间戳(u64) */
err = nvs_set_u64(handle, "last_ts", s_pet_status.last_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 last_ts 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "is_travelling", s_pet_status.is_travelling);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 is_travelling 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "is_feed_cd", s_pet_status.is_feed_cd);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 is_feed_cd 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u64(handle, "travel_ts", s_pet_status.travel_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 travel_timestamp 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u64(handle, "feed_timestamp", s_pet_status.feed_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 feed_timestamp 失败: %s", esp_err_to_name(err));
}
err = nvs_commit(handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "NVS 提交失败: %s", esp_err_to_name(err));
}
nvs_close(handle);
}
static void pet_load_status_from_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
uint8_t hunger = 0;
uint8_t cleanliness = 0;
uint8_t mood = 0;
uint16_t intimacy = 0;
uint8_t is_travelling = 0;
uint8_t is_feed_cd = 0;
esp_err_t err_hunger = nvs_get_u8(handle, "hunger", &hunger);
esp_err_t err_cleanliness = nvs_get_u8(handle, "cleanliness", &cleanliness);
esp_err_t err_mood = nvs_get_u8(handle, "mood", &mood);
esp_err_t err_intimacy = nvs_get_u16(handle, "intimacy", &intimacy);
esp_err_t err_last_ts = nvs_get_u64(handle, "last_ts", &s_pet_status.last_timestamp);
esp_err_t err_is_travelling = nvs_get_u8(handle,"is_travelling",&is_travelling);
if (err_is_travelling == ESP_OK) {
s_pet_status.is_travelling = is_travelling;
};
esp_err_t err_travel_timestamp = nvs_get_u64(handle,"travel_ts",&s_pet_status.travel_timestamp);
esp_err_t err_is_feed_cd = nvs_get_u8(handle,"is_feed_cd",&is_feed_cd);
if (err_is_feed_cd == ESP_OK) {
s_pet_status.is_feed_cd = is_feed_cd;
};
esp_err_t err_feed_timestamp = nvs_get_u64(handle,"feed_timestamp",&s_pet_status.feed_timestamp);
if (err_hunger == ESP_OK && err_cleanliness == ESP_OK && err_mood == ESP_OK && err_intimacy == ESP_OK && err_last_ts == ESP_OK) {
ESP_LOGW(TAG, "打开 NVSdata 成功,进入读取分支 ");
s_pet_status.hunger = clamp_u8(hunger, 0, 100);
s_pet_status.cleanliness = clamp_u8(cleanliness, 0, 100);
s_pet_status.mood = clamp_u8(mood, 0, 100);
s_pet_status.intimacy = clamp_u16(intimacy, 0, 300);
/* 将 u64 转换为 struct tm(本地时区) */
time_t t = (time_t)s_pet_status.last_timestamp;
localtime_r(&t, &s_pet_status.last_time);
g_pet_need_first_sync = false;
nvs_close(handle);
}else{
ESP_LOGW(TAG, "打开 NVSdata 失败,进入初始化分支 ");
s_pet_status.hunger = 0;
s_pet_status.cleanliness = 0;
s_pet_status.mood = 0;
s_pet_status.intimacy = 0;
nvs_set_u8(handle, "hunger", s_pet_status.hunger);
nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness);
nvs_set_u8(handle, "mood", s_pet_status.mood);
nvs_set_u16(handle, "intimacy", s_pet_status.intimacy);
nvs_set_u8(handle,"is_travelling",s_pet_status.is_travelling);
nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp);
nvs_set_u8(handle,"is_feed_cd",s_pet_status.is_feed_cd);
nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp);
err = nvs_commit(handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "初始化 NVS pet_data 提交失败: %s", esp_err_to_name(err));
}
g_pet_need_first_sync = true; // 标记需要第一次时间同步
nvs_close(handle);
}
return;
}
pet_stage_t pet_get_stage(uint16_t intimacy)
{
if (intimacy >= 200) {
return PET_STAGE_ADULT;
}
if (intimacy >= 100) {
return PET_STAGE_TEEN;
}
return PET_STAGE_BABY;
}
static void pet_update_stage(void)
{
s_pet_status.stage = pet_get_stage(s_pet_status.intimacy);
}
static void pet_update_can_travel(void)
{
if (s_pet_status.is_travelling) {
s_pet_status.can_travel = false;
return;
}
if (s_pet_status.mood >= 60 && s_pet_status.intimacy >= 30 && s_pet_status.hunger >= 30) {
s_pet_status.can_travel = true;
} else {
s_pet_status.can_travel = false;
}
}
static void pet_update_travel_time(void)
{
switch (s_pet_status.vehicle) {
case PET_VEHICLE_WALK:
s_pet_status.travel_time = 30 * 60; /* 30 minutes */
break;
case PET_VEHICLE_BIKE:
s_pet_status.travel_time = 60 * 60; /* 60 minutes */
break;
case PET_VEHICLE_MOTORCYCLE:
s_pet_status.travel_time = 3 * 60 * 60; /* 3 hours */
break;
case PET_VEHICLE_CAR:
s_pet_status.travel_time = 6 * 60 * 60; /* 6 hours */
break;
case PET_VEHICLE_PLANE:
s_pet_status.travel_time = 12 * 60 * 60; /* 12 hours */
break;
default:
s_pet_status.travel_time = 30 * 60;
break;
}
}
pet_vehicle_t pet_get_vehicle_stage(uint16_t intimacy)
{
if (intimacy >= 200) {
return PET_VEHICLE_PLANE;
}
if (intimacy >= 150) {
return PET_VEHICLE_CAR;
}
if (intimacy >= 100) {
return PET_VEHICLE_MOTORCYCLE;
}
if (intimacy >= 50) {
return PET_VEHICLE_BIKE;
}
return PET_VEHICLE_WALK;
}
static void pet_update_vehicle_stage(void)
{
s_pet_status.vehicle = pet_get_vehicle_stage(s_pet_status.intimacy);
}
static const char *pet_stage_name(pet_stage_t stage)
{
switch (stage) {
case PET_STAGE_BABY:
return "幼崽";
case PET_STAGE_TEEN:
return "少年";
case PET_STAGE_ADULT:
return "成年";
default:
return "未知";
}
}
static const char *pet_stage_vehicle_name(pet_vehicle_t stage)
{
switch (stage) {
case PET_VEHICLE_WALK:
return "步行";
case PET_VEHICLE_BIKE:
return "单车";
case PET_VEHICLE_MOTORCYCLE:
return "摩托";
case PET_VEHICLE_CAR:
return "汽车";
case PET_VEHICLE_PLANE:
return "飞机";
default:
return "未知";
}
}
static void pet_log_status(const char *action)
{
ESP_LOGI(TAG, "%s: 饱腹=%u, 清洁=%u, 心情=%u, 亲密度=%u, 形态=%s, 出行方式=%s",
action,
s_pet_status.hunger,
s_pet_status.cleanliness,
s_pet_status.mood,
s_pet_status.intimacy,
pet_stage_name(s_pet_status.stage),
pet_stage_vehicle_name(s_pet_status.vehicle));
}
void pet_init(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
nvs_set_u8(handle, "hunger", 30);
nvs_set_u8(handle, "cleanliness", 30);
nvs_set_u8(handle, "mood", 30);
nvs_set_u8(handle,"is_travelling",0);
nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp);
nvs_set_u8(handle,"is_feed_cd",0);
nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp);
err = nvs_commit(handle);
nvs_close(handle);
pet_load_status_from_nvs();
pet_update_stage();
pet_update_vehicle_stage();
ESP_LOGI(TAG, "宠物加载完成,当前形态=%s,出行方式=%s",
pet_stage_name(s_pet_status.stage),
pet_stage_vehicle_name(s_pet_status.vehicle));
pet_log_status("加载后状态");
pet_task_stack = heap_caps_malloc(PET_TASK_STACK_SIZE, MALLOC_CAP_INTERNAL);
if (pet_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate PET task stack");
return;
}
write_nvs_task_stack = heap_caps_malloc(HOUR_WRITE_STACK_SIZE, MALLOC_CAP_INTERNAL);
if (write_nvs_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate PET_WRITE task stack");
return;
}
user_interact_Queue = xQueueCreate(5, sizeof(int));
if (user_interact_Queue == NULL) {
ESP_LOGE(TAG, "Failed to create user_interact_Queue");
return;
}
xTaskCreateStaticPinnedToCore(pet_time_sync_task, "PET", PET_TASK_STACK_SIZE, NULL, PET_TASK_PRIORITY, pet_task_stack, &pet_task_tcb, 1);
xTaskCreateStaticPinnedToCore(pet_user_write_task, "PET_WRITE", HOUR_WRITE_STACK_SIZE, NULL, HOUR_WRITE_TASK_PRIORITY, write_nvs_task_stack, &write_nvs_task_tcb, 1);
}
const pet_status_t *pet_get_status(void)
{
return &s_pet_status;
}
/*交互动作数值计算*/
void pet_feed(void)
{
s_pet_status.hunger = clamp_u8(s_pet_status.hunger + 30, 0, 100);
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("喂食");
}
void pet_bath(void)
{
s_pet_status.cleanliness = clamp_u8(s_pet_status.cleanliness + 100, 0, 100);
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("洗澡");
}
void pet_interact(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 1, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("点击互动");
}
void pet_travel_return(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 5, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("旅行归来");
}
void cal_deacy(uint64_t elapsed){
if(elapsed> 172800){
s_pet_status.hunger = 0;
s_pet_status.cleanliness = 0;
s_pet_status.mood = 0;
}
else{
uint8_t decay_hunger = elapsed / 450;
uint8_t decay_cleanliness = elapsed / 1200;
uint8_t decay_mood = elapsed / 1500;
uint8_t remaining_hunger = elapsed % 450;
uint8_t remaining_cleanliness = elapsed % 1200;
uint8_t remaining_mood = elapsed % 1500;
s_pet_status.hunger = (decay_hunger >= s_pet_status.hunger) ? 0 : s_pet_status.hunger - decay_hunger;
s_pet_status.cleanliness = (decay_cleanliness >= s_pet_status.cleanliness) ? 0 : s_pet_status.cleanliness - decay_cleanliness;
s_pet_status.mood = (decay_mood >= s_pet_status.mood) ? 0 : s_pet_status.mood - decay_mood;
pet_log_status("加载后状态");
}
}
static void compare_time_cd(uint64_t now){
if(s_pet_status.is_feed_cd){
uint64_t elapsed = now - s_pet_status.feed_timestamp;
if(elapsed > 14400){
s_pet_status.is_feed_cd = false;
s_pet_status.feed_timestamp = 0;
}
}
if(s_pet_status.is_travelling){
uint64_t elapsed = now - s_pet_status.travel_timestamp;
if(elapsed > s_pet_status.travel_time){
s_pet_status.is_travelling = false;
s_pet_status.travel_timestamp = 0;
}
}
return;
}
static void feed_ready_handler(TimerHandle_t xTimer) {
s_pet_status.is_feed_cd = false;
s_pet_status.feed_timestamp = 0;
ESP_LOGW("PET", "进入定时器结算");
pet_save_status_to_nvs();
feed_timer = NULL;
xTimerDelete(xTimer, 0);
}
static void travel_ready_handler(TimerHandle_t xTimer) {
s_pet_status.is_travelling = false;
s_pet_status.travel_timestamp = 0;
ESP_LOGW("PET", "进入定时器结算");
pet_save_status_to_nvs();
travel_timer = NULL;
xTimerDelete(xTimer, 0);
}
/*
* 时间同步任务骨架
* - 如果无网络/未能 NTP,同步失败后进入休眠/等待
* - 同步成功后设置 g_pet_animation_ready
* - 如果是第一次初始化(g_pet_need_first_sync==true),写入当前时间到 NVS
* - 否则根据过去时间计算状态衰减(此处留占位注释,具体公式可在 TODO 中实现)
*/
static void pet_time_sync(void){
if (!g_wifi_state){
// 无网络则休眠,等待外部事件唤醒或下次循环
ESP_LOGE("PET", "无WIFI进入待机状态");
vTaskDelay(pdMS_TO_TICKS(5000));
}
else{
g_pet_ntp_synced = obtain_time();
if(!g_pet_ntp_synced){
ESP_LOGE("PET", "有WIFI但网络同步失败,进入休眠状态");
vTaskDelay(pdMS_TO_TICKS(5000));
}
else{
ESP_LOGW("PET", "有WIFI且时钟同步成功,开启动画窗口");
time_t now;
time(&now);
if (g_pet_need_first_sync) {
ESP_LOGW("PET", "第一次同步,写入当前时间到 NVS");
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
g_pet_need_first_sync = false;
g_pet_animation_ready = true;
}else {
ESP_LOGW("PET", "不是第一次同步,计算过去时间并衰减属性");
uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp;
ESP_LOGW("PET", "相比之前过去了%llu秒",elapsed);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
compare_time_cd((uint64_t)now);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &s_pet_status.last_time);//覆盖上次启动时间
ESP_LOGI("NTP", "同步时间: %s", strftime_buf);
cal_deacy(elapsed);
pet_save_status_to_nvs();
pet_update_stage();
pet_update_vehicle_stage();
g_pet_animation_ready = true;
}
}
}
}
static void pet_time_timer(void){
const TickType_t delay_ms = pdMS_TO_TICKS(60000*5); // 5 分钟间隔
TickType_t last_wake = xTaskGetTickCount();
uint32_t tick_count = 0;
int times_hunger;
int times_cleanliness;
int times_mood;
while (1) {
vTaskDelayUntil(&last_wake, delay_ms);
tick_count++;
ESP_LOGI("NTP", "五分钟间隔");
time_t now;
time(&now);
//uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp;
uint8_t compensate_tickcount_hunger= remaining_hunger / 300; //掉电补偿
uint8_t compensate_tickcount_cleanliness= remaining_cleanliness / 300; //掉电补偿
uint8_t compensate_tickcount_mood= remaining_mood / 300;
int cmd1=1;int cmd2=2;int cmd3=3;int cmd4=4;
if(s_pet_status.is_feed_cd){
uint64_t elapsed = (uint64_t)now - s_pet_status.feed_timestamp;
if(elapsed > s_pet_status.feed_time - 300){ //最后五分钟
feed_timer = xTimerCreate("feed_ready_can_use", pdMS_TO_TICKS((s_pet_status.feed_time-elapsed)*1000),
pdFALSE, (void*)0,
feed_ready_handler);
xTimerStart(feed_timer, 0);
ESP_LOGW("PET", "即将完成喂食冷却,启用高精度定时");
}
}
if(s_pet_status.is_travelling){
uint64_t elapsed = (uint64_t)now - s_pet_status.travel_timestamp;
if(elapsed > s_pet_status.travel_time - 300){ //最后五分钟
travel_timer = xTimerCreate("travel_ready_done", pdMS_TO_TICKS((s_pet_status.travel_time-elapsed)*1000),
pdFALSE, (void*)0,
travel_ready_handler);
xTimerStart(travel_timer, 0);
ESP_LOGW("PET", "即将完成旅行冷却,启用高精度定时");
}
}
/* (15 分钟) 饱腹 -2 */
if ((tick_count + compensate_tickcount_hunger) % 3 == 0) {
if (s_pet_status.hunger > 2) s_pet_status.hunger -= 2;
else s_pet_status.hunger = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
remaining_hunger = 0;
ESP_LOGW("PET", "饱腹-2");
}
/* (20 分钟) 清洁 -1 */
if ((tick_count + compensate_tickcount_cleanliness) % 4 == 0) {
if (s_pet_status.cleanliness > 0) s_pet_status.cleanliness -= 1;
else s_pet_status.cleanliness = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
ESP_LOGW("PET", "清洁-1");
}
/* (25 分钟) 心情 -1 */
if ((tick_count + compensate_tickcount_cleanliness)% 5 == 0) {
if (s_pet_status.mood > 0) s_pet_status.mood -= 1;
else s_pet_status.mood = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
ESP_LOGW("PET", "心情-1");
}
if(tick_count % 2 ==0 && go_travelling() && s_pet_status.can_travel){//缺少场景模式判断
xQueueSend(user_interact_Queue, &cmd4,0);
}
/* 每 12 次(60 分钟) 写入 NVS */
if ((tick_count % 12) == 0) {
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
ESP_LOGI(TAG, "Hourly NVS write (tick %u)", tick_count);
}
}
}
static void pet_time_sync_task(void *arg)
{
while (1){
uxTaskGetStackHighWaterMark(NULL);
if(g_pet_animation_ready == false){
pet_time_sync();
}else{
pet_time_timer();
}
}
}
static void pet_user_write_task(void *arg)
{
int received_cmd = 0;
while (1) {
/* 阻塞直到收到数据,等待用户命令 */
uxTaskGetStackHighWaterMark(NULL);
if (xQueueReceive(user_interact_Queue, &received_cmd, portMAX_DELAY) == pdPASS) {
ESP_LOGI(TAG, "pet_user_write_task: received command %d", received_cmd);
switch (received_cmd) {
case 1:
pet_feed();
ESP_LOGI(TAG, "命令1执行");
break;
case 2:
pet_bath();
ESP_LOGI(TAG, "命令2执行");
break;
case 3:
pet_interact();
ESP_LOGI(TAG, "命令3执行");
break;
case 4:
pet_travel_return();
//pet_start_travelling();
ESP_LOGI(TAG, "命令4执行");
break;
default:
ESP_LOGW(TAG, "未知用户命令 %d", received_cmd);
break;
}
}
}
}
void pet_start_travelling(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100);
}
+128
View File
@@ -0,0 +1,128 @@
#ifndef PET_INIT_H
#define PET_INIT_H
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "main.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
PET_STAGE_BABY = 0,
PET_STAGE_TEEN,
PET_STAGE_ADULT,
} pet_stage_t;
typedef enum {
PET_VEHICLE_WALK = 0,
PET_VEHICLE_BIKE,
PET_VEHICLE_MOTORCYCLE,
PET_VEHICLE_CAR,
PET_VEHICLE_PLANE,
} pet_vehicle_t;
typedef enum {
PARK = 0,
STREET, // 街道
RIVER, // 河流
ANCIENT_TOWN, // 古镇
MOUNTAIN_TRAIL, // 山道
RESERVOIR, // 水库
SETTLEMENT, // 聚落
ORCHARD, // 果园
HUIZHOU, // 惠州
DONGGUAN, // 东莞
FOSHAN, // 佛山
ZHONGSHAN, // 中山
JIANGMEN, // 江门
QINGYUAN, // 清远
ZHAOQING, // 肇庆
HEYUAN, // 河源
MEIZHOU, // 梅州
CHONGQING, // 重庆
XIAN, // 西安
GUANGZHOU, // 广州
NANJING, // 南京
SANYA, // 三亚
BEIJING, // 北京
CHANGSHA, // 长沙
ZHANGJIAJIE, // 张家界
XIANGXI, // 湘西
FENGHUANG_GUZHEN, // 凤凰古镇
SHANGHAI, // 上海
XIANGGELILA, // 香格里拉
LIJIANG, // 丽江
DALI, // 大理
XIZANG, // 西藏
XIAMEN, // 厦门
JINGDEZHEN, // 景德镇
PINGTAN, // 平潭
QINGDAO, // 青岛
BEIHAI, // 北海
WANNING, // 万宁
ZIBO, // 淄博
YANJI, // 延吉
LIUZHOU, // 柳州
YICHANG, // 宜昌
MOHE, // 漠河
GUILIN, // 桂林
HANGZHOU, // 杭州
XITANG, // 西塘
QINGHAI, // 青海
XISHUANGBANNA, // 西双版纳
DUNHUANG, // 敦煌
YUNNAN, // 云南(省/区域)
CITY_COUNT, // 枚举计数(必须放最后)
} travel_city_t;
extern const char *city_names[CITY_COUNT];
typedef struct {
uint8_t hunger; // 0-100
uint8_t cleanliness; // 0-100
uint8_t mood; // 0-100
uint16_t intimacy; // 0-300
pet_stage_t stage;
pet_vehicle_t vehicle;
uint64_t last_timestamp; // UTC seconds stored as u64
struct tm last_time; // 本地时间表示
bool is_travelling; //是否在旅行
uint64_t travel_timestamp;
bool can_travel;
uint16_t travel_time;
uint16_t feed_time; //喂食状态
uint64_t feed_timestamp;
bool is_feed_cd;
} pet_status_t;
/* 全局标志(在 pet_init.c 中定义) */
extern bool g_pet_need_first_sync; // true 表示需要第一次 NTP 同步并写入时间
extern bool g_pet_ntp_synced; // NTP 同步成功
extern bool g_pet_animation_ready; // 同步成功后,动画可用标志
/* 对外接口 */
void pet_init(void);
const pet_status_t *pet_get_status(void);
pet_stage_t pet_get_stage(uint16_t intimacy);
pet_vehicle_t pet_get_vehicle_stage(uint16_t intimacy);
void pet_feed(void);
void pet_bath(void);
void pet_interact(void);
void pet_travel_return(void);
/* 时间同步任务控制 */
void pet_start_time_task(void);
extern bool g_wifi_state;
#ifdef __cplusplus
}
#endif
#endif
+989
View File
@@ -0,0 +1,989 @@
#include <stdio.h>
#include <inttypes.h>
#include "../config.h"
#if !EXAMPLE_USE_TOUCH
#error "SC7U22 requires EXAMPLE_USE_TOUCH (IMU shares the touch I2C bus)."
#endif
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include <math.h>
#include "esp_timer.h"
#include "../ui/generated/gui_guider.h" // 包含GUI引导器头文件
#include "lvgl.h" // 包含LVGL头文件
#include "LVGL/lvgl_init.h"
#include "ui_cmd.h"
#include "main.h"
static bool event_cooldown_active = false; // 是否处于冷却屏蔽中
static int64_t cooldown_end_time = 0; // 冷却结束时间戳
static const char *TAG_IMU = "SCU722";
i2c_master_dev_handle_t SC7U22_handle;
/** 与触摸共用的 I2C 总线句柄(SC7U22_init 写入,供扫描/查询) */
i2c_master_bus_handle_t s_i2c_bus;
sensor_data_t data_buffer[DATA_BUFFER_SIZE];
SC7U22_DataType sensor_data = {0};
static int buffer_index = 0;
static int buffer_count = 0;
struct acc_offset offset_data = {0};
noise_datatype noise_data = {0};
float ax=0,ay=0,az=0,gx=0,gy=0,gz=0;
//float acc_pre_x=0,acc_pre_y=0,acc_pre_z=0;
float acc_sum = 0;
float acc_sign_sum = 0;
float acc_hp = 0;
uint32_t bum_energy = 0;
float iir_state = 0;
uint16_t start_stop_cnt = 0;
uint16_t stop_cnt = 0;
uint8_t sharp_turn_flag = 0,pre_st_flag = 0;
bool sharpturn_ret_L = false,sharpturn_ret_R = false;
uint8_t rapid_acc_flag = 0,pre_ra_flag = 0,rapid_acc_ret = 0;
uint8_t rapid_dec_flag = 0,pre_rd_flag = 0,rapid_dec_ret = 0;
bool brake_fir_flag = false, rapid_fir_flag = false;
const char* MYTAG = "SC7U22";
extern int lottie_id;
extern int current_page_idx;
extern lv_ui guider_ui; // 外部声明GUI引导器
/*
* @brief This function converts lsb to meter per second squared for 16 bit accelerometer at
* range 2G, 4G, 8G or 16G.
*/
float lsb_to_mps2(int16_t val, float g_range, uint8_t bit_width)
{
double power = 2;
float half_scale = (float)((pow((double)power, (double)bit_width) / 2.0f));
return (GRAVITY_EARTH * val * g_range) / half_scale;
}
/*
* @brief This function converts lsb to degree per second for 16 bit gyro at
* range 125, 250, 500, 1000 or 2000dps.
*/
float lsb_to_dps(int16_t val, float dps, uint8_t bit_width)
{
double power = 2;
float half_scale = (float)((pow((double)power, (double)bit_width) / 2.0f));
return (dps / (half_scale)) * (val);
}
void i2c_scan_devices(void)
{
if (s_i2c_bus == NULL) {
ESP_LOGW(MYTAG, "i2c_scan: bus not ready (call SC7U22_init first)");
return;
}
uint8_t address;
esp_err_t ret;
bool found = false;
ESP_LOGI(MYTAG, "Scanning I2C devices on shared bus...");
for (address = 1; address < 127; address++) {
ret = i2c_master_probe(s_i2c_bus, (uint16_t)address, 100);
if (ret == ESP_OK) {
ESP_LOGI(MYTAG, "Device found at address: 0x%02X", address);
found = true;
}
vTaskDelay(50 / portTICK_PERIOD_MS);
}
if (!found) {
ESP_LOGI(MYTAG, "No I2C devices found");
}
}
void SC7U22_init(i2c_master_bus_handle_t i2c_bus)
{
if (i2c_bus == NULL) {
ESP_LOGE(MYTAG, "SC7U22_init: I2C bus handle is NULL");
return;
}
s_i2c_bus = i2c_bus;
ESP_LOGI(MYTAG, "SC7U22 on shared I2C bus: %p", (void *)s_i2c_bus);
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = SC7U22_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &SC7U22_handle);
if (ret != ESP_OK) {
ESP_LOGE(MYTAG, "添加设备失败,错误码: %s", esp_err_to_name(ret));
return;
}
SC7U22_Sensor_enable();
ESP_LOGI(MYTAG, "SC7U22 初始化完成(硬件就绪,校准将在后台进行)");
}
esp_err_t SC7U22_Reg_W(uint8_t reg_addr, uint8_t data)
{
uint8_t write_buf[2] = {reg_addr, data};
return i2c_master_transmit(SC7U22_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
esp_err_t SC7U22_Reg_R(uint8_t reg_addr, uint8_t *data)
{
return i2c_master_transmit_receive(SC7U22_handle, &reg_addr, 1, data, 1, 1000 / portTICK_PERIOD_MS);
}
void SC7U22_Sensor_enable(void)
{
SC7U22_Reg_W(0x7F, 0x00); // Bank / page(如需要)
vTaskDelay(pdMS_TO_TICKS(2));
SC7U22_Reg_W(0x7D, 0x0E); // PWR_CTRL enable acc + gyro
vTaskDelay(pdMS_TO_TICKS(5));
// 加速度
SC7U22_Reg_W(0x40, 0xA8); // ACC_CONF(用缺省)
SC7U22_Reg_W(0x41, 0x02); // ±4g
// 陀螺仪(关键)
SC7U22_Reg_W(0x42, 0xA9); // GYR_CONF(缺省、安全)
SC7U22_Reg_W(0x43, 0x00); // ±2000 dps
}
void SC7U22_DaMYTAGet(SC7U22_DataType* SC7U22_DataStucture)
{
esp_err_t ret;
uint8_t raw_acc[6]; // 用于存放 AX_L, AX_H, AY_L, AY_H, AZ_L, AZ_H
uint8_t raw_gyr[6]; // 用于存放 GX_L, GX_H, GY_L, GY_H, GZ_L, GZ_H
// ================= 加速度计读取 (一次读6字节) =================
// 假设 XOUT_L 是起始地址。如果你的宏定义 XOUT_H 比 XOUT_L 小,请改用 XOUT_H
uint8_t acc_reg_addr = SC7U22_ACCEL_XOUT_H;
ret = i2c_master_transmit_receive(SC7U22_handle, &acc_reg_addr, 1, raw_acc, 6, 1000 / portTICK_PERIOD_MS);
if (ret == ESP_OK) {
// 拼接数据。注意:通常 I2C 传感器先发低位(L),后发高位(H)。
// 具体的 buffer 顺序取决于寄存器地址顺序。
// 假设地址顺序是: X_L(0), X_H(1), Y_L(2), Y_H(3), Z_L(4), Z_H(5)
SC7U22_DataStucture->Ax = (int16_t)((raw_acc[0] << 8) | raw_acc[1]);
SC7U22_DataStucture->Ay = (int16_t)((raw_acc[2] << 8) | raw_acc[3]);
SC7U22_DataStucture->Az = (int16_t)((raw_acc[4] << 8) | raw_acc[5]);
} else {
ESP_LOGE(MYTAG, "Accel read failed: %s", esp_err_to_name(ret));
}
// ================= 陀螺仪读取 (一次读6字节) =================
// 假设 XOUT_L 是起始地址
uint8_t gyr_reg_addr = SC7U22_GYRO_XOUT_H;
//暂时注释掉
ret = i2c_master_transmit_receive(SC7U22_handle, &gyr_reg_addr, 1, raw_gyr, 6, 1000 / portTICK_PERIOD_MS);
if (ret == ESP_OK) {
SC7U22_DataStucture->Gx = (int16_t)((raw_gyr[0] << 8) | raw_gyr[1]);
SC7U22_DataStucture->Gy = (int16_t)((raw_gyr[2] << 8) | raw_gyr[3]);
SC7U22_DataStucture->Gz = (int16_t)((raw_gyr[4] << 8) | raw_gyr[5]);
} else {
ESP_LOGE(MYTAG, "Gyro read failed");
}
}
void baseline_remove(void)
{
// 加速度计去偏
ax = lsb_to_mps2(sensor_data.Ax, (float)4, 16) - offset_data.acc_x_offset;
ay = lsb_to_mps2(sensor_data.Ay, (float)4, 16) - offset_data.acc_y_offset;
az = lsb_to_mps2(sensor_data.Az, (float)4, 16) - offset_data.acc_z_offset;
// 陀螺仪去偏 (新增)
gx = lsb_to_dps(sensor_data.Gx, (float)2000, 16) - offset_data.gyr_x_offset;
gy = lsb_to_dps(sensor_data.Gy, (float)2000, 16) - offset_data.gyr_y_offset;
gz = lsb_to_dps(sensor_data.Gz, (float)2000, 16) - offset_data.gyr_z_offset;
}
void noise_threshold_get(void)
{
uint8_t ax_cnt = 0,ay_cnt = 0,az_cnt = 0,gx_cnt = 0,gy_cnt = 0,gz_cnt = 0;
SC7U22_DaMYTAGet(&sensor_data);
baseline_remove();
for(int i = 0;i<50;i++)
{
if(ax!=0) {noise_data.accx_noise+=fabs(ax); ax_cnt++;}
if(ay!=0) {noise_data.accy_noise+=fabs(ay); ay_cnt++;}
if(az!=0) {noise_data.accz_noise+=fabs(az); az_cnt++;}
if(gx!=0) {noise_data.gyrx_noise+=fabs(gx); gx_cnt++;}
if(gy!=0) {noise_data.gyry_noise+=fabs(gy); gy_cnt++;}
if(gz!=0) {noise_data.gyrz_noise+=fabs(gz); gz_cnt++;}
vTaskDelay(pdMS_TO_TICKS(5));
}
noise_data.accx_noise/=(float)ax_cnt;
noise_data.accy_noise/=(float)ay_cnt;
noise_data.accz_noise/=(float)az_cnt;
noise_data.gyrx_noise/=(float)gx_cnt;
noise_data.gyry_noise/=(float)gy_cnt;
noise_data.gyrz_noise/=(float)gz_cnt;
}
void data_deal_with_noise(void)
{
if(fabs(ax)<fabs(noise_data.accx_noise)*1.5f+0.05) ax = 0;
if(fabs(ay)<fabs(noise_data.accy_noise)*1.5f+0.05) ay = 0;
if(fabs(az)<fabs(noise_data.accz_noise)*1.5f+0.05) az = 0;
if(fabs(gx)<fabs(noise_data.gyrx_noise)*1.5f+1) gx = 0;
if(fabs(gy)<fabs(noise_data.gyry_noise)*1.5f+1) gy = 0;
if(fabs(gz)<fabs(noise_data.gyrz_noise)*1.5f+1) gz = 0;
}
void mySC7_getdata(void)
{
SC7U22_DaMYTAGet(&sensor_data);
baseline_remove(); //去零偏处理
data_deal_with_noise();//去噪声处理
data_buffer[buffer_index].accel_x = ax;
data_buffer[buffer_index].accel_y = ay;
data_buffer[buffer_index].accel_z = az;
data_buffer[buffer_index].timestamp = esp_timer_get_time();
buffer_index = (buffer_index + 1) % DATA_BUFFER_SIZE;
if (buffer_count < DATA_BUFFER_SIZE) {
buffer_count++;
}
}
void accel_Getoffset(void)
{
float ax_sum=0, ay_sum=0, az_sum=0;
float gx_sum=0, gy_sum=0, gz_sum=0; // 新增
ESP_LOGI(MYTAG, "正在校准传感器,请保持静止...");
for(int i = 0; i < 100; i++) // 采样100次
{
SC7U22_DaMYTAGet(&sensor_data);
// 累加加速度
ax_sum += lsb_to_mps2(sensor_data.Ax, (float)4, 16);
ay_sum += lsb_to_mps2(sensor_data.Ay, (float)4, 16);
az_sum += lsb_to_mps2(sensor_data.Az, (float)4, 16);
// 累加陀螺仪 (注意:这里使用 2000dps 量程)
gx_sum += lsb_to_dps(sensor_data.Gx, (float)2000, 16);
gy_sum += lsb_to_dps(sensor_data.Gy, (float)2000, 16);
gz_sum += lsb_to_dps(sensor_data.Gz, (float)2000, 16);
vTaskDelay(pdMS_TO_TICKS(10));
}
// 计算平均值
offset_data.acc_x_offset = ax_sum / 100.0f;
offset_data.acc_y_offset = ay_sum / 100.0f;
offset_data.acc_z_offset = (az_sum / 100.0f) - GRAVITY_EARTH; // Z轴要扣除重力
// 计算陀螺仪偏移 (理论上静止时全是0,所以读数即为偏移)
offset_data.gyr_x_offset = gx_sum / 100.0f;
offset_data.gyr_y_offset = gy_sum / 100.0f;
offset_data.gyr_z_offset = gz_sum / 100.0f;
int gz_milli = (int)(offset_data.gyr_z_offset * 1000.0f);
ESP_LOGI(MYTAG, "校准完成! Gz Offset: %d.%03d", gz_milli / 1000, abs(gz_milli % 1000));
}
void bum_energy_get(void)
{
acc_sum += fabs(az);
acc_sign_sum += az;
iir_state = ALPHA * iir_state + (1 - ALPHA) * acc_sum;
acc_hp = acc_sum -iir_state; //高通滤波
bum_energy += (uint32_t)(acc_hp*acc_hp*1000); //处理颠簸能量
}
bool is_bump(void)
{
uint32_t temp = bum_energy;
static bool wasbump = false;
float variance = 0;
bum_energy = 0;
acc_sum = 0;
acc_sign_sum /= DATA_BUFFER_SIZE;
for(int i = 0;i<DATA_BUFFER_SIZE;i++)
{
variance += fabs(data_buffer[i].accel_z - acc_sign_sum); //平均绝对值误差
}
acc_sign_sum = 0;
bool isbump = (temp>=BUM_THRESHOLD&&variance>=400);
if(isbump&&!wasbump)
{
wasbump = isbump;
return true;
}
wasbump = isbump;
return false;
}
void sharp_turn_detect(void)
{
if(fabs(gz) > turn_gyro_threshold && fabs(ay) > turn_accel_threshold)
{
sharp_turn_flag++;
}
else
{
sharp_turn_flag = 0;
}
if(sharp_turn_flag == 0 && pre_st_flag >= 5) // 连续触发次数稍微减少一点,灵敏些
{
pre_st_flag = 0;
// 左转通常 gyrz > 0 (逆时针),右转 gyrz < 0 (顺时针)
// 具体正负取决于芯片安装面,你测试时如果不准,互换一下即可
if(gz > 0) sharpturn_ret_L = true;
else if(gz < 0) sharpturn_ret_R = true;
}
pre_st_flag = sharp_turn_flag;
}
driving_event_t is_sharp_turn(void)
{
if(sharpturn_ret_L)
{
sharpturn_ret_L = 0;
return EVENT_SHARP_LTURN;
}
else if(sharpturn_ret_R)
{
sharpturn_ret_R = 0;
return EVENT_SHARP_RTURN;
}
return EVENT_NONE;
}
static float calculate_acceleration_magnitude(void)
{
return sqrtf(ax * ax + ay * ay + az * az);
}
void start_stop_state(void)
{
static uint8_t st_cnt = 0;
static uint8_t sp_cnt = 0;
static bool stop_flag = false;
bool is_stop = calculate_acceleration_magnitude()<= is_stop_magnitude;
if(is_stop&&!stop_flag) sp_cnt++;
if(sp_cnt>=3)
{
stop_flag = true;
sp_cnt = 0;
}
if(is_stop&&stop_flag)
{
stop_cnt++;
}
if(stop_flag&&!is_stop)
{
st_cnt++;
}
else{
st_cnt = 0;
}
if(st_cnt>=3)
{
start_stop_cnt++;
st_cnt = 0;
stop_flag = false;
}
}
// 判断急停急加速
void detect_driving_events(void)
{
//获取数据
mySC7_getdata();
//颠簸能量累加
bum_energy_get();
//急转弯检测
sharp_turn_detect();
//急加速检测 急刹车检测
if (buffer_count < 2) return ;
int current_idx = (buffer_index - 1 + DATA_BUFFER_SIZE) % DATA_BUFFER_SIZE;
int prev_idx = (buffer_index - 2 + DATA_BUFFER_SIZE) % DATA_BUFFER_SIZE;
// 计算时间差(秒)
float time_diff = (data_buffer[current_idx].timestamp -
data_buffer[prev_idx].timestamp) / 1000000.0f;
if (time_diff <= 0) return ;
float accel_diff_x = data_buffer[current_idx].accel_x - data_buffer[prev_idx].accel_x;
float jerk_x = accel_diff_x / time_diff; // 急动度
// 急刹车/减速检测
// 逻辑:当前有一个很大的负向加速度(刹车),且变化率很大
// 注意:如果是“前倾 accx 变大”,说明 +X 是车头向下或向前。
// 假设 +X 是向前加速,那么 -X 就是刹车。
if ((jerk_x < -rapid_accel_threshold/0.02) && (data_buffer[current_idx].accel_x < -rapid_accel_threshold))
rapid_dec_flag = 1;
if ((data_buffer[current_idx].accel_x < -rapid_accel_threshold) && rapid_dec_flag)
rapid_dec_flag++;
else
rapid_dec_flag = 0;
if (rapid_dec_flag == 0 && pre_rd_flag >= 4) rapid_dec_ret = 1; // 阈值改小一点容易触发
pre_rd_flag = rapid_dec_flag;
// 急加速检测
// 逻辑:当前有一个很大的正向加速度
if ((jerk_x > rapid_accel_threshold/0.02) && (data_buffer[current_idx].accel_x > rapid_accel_threshold))
rapid_acc_flag = 1;
if ((data_buffer[current_idx].accel_x > rapid_accel_threshold) && rapid_acc_flag)
rapid_acc_flag++;
else
rapid_acc_flag = 0;
if (rapid_acc_flag == 0 && pre_ra_flag >= 4) rapid_acc_ret = 1;
pre_ra_flag = rapid_acc_flag;
}
driving_event_t is_rapid_acc_or_dec(void)
{
if(rapid_acc_ret)
{
rapid_acc_ret = 0;
return EVENT_SUDDEN_ACCEL;
}
if(rapid_dec_ret)
{
rapid_dec_ret = 0;
return EVENT_SUDDEN_BRAKE;
}
return EVENT_NONE;
}
bool is_jam(void)
{
float stop_proportion = (float)stop_cnt/600.0;
uint16_t temp_cnt = start_stop_cnt;
stop_cnt = 0;
start_stop_cnt = 0;
if((stop_proportion>=stop_proportion_threshold&&temp_cnt>=start_stop_threshold)||stop_proportion>=0.8f)
{
return true;
}
return false;
}
// 在IMU任务函数外定义(全局区),避免栈溢出,同时供回调函数访问
// 驾驶事件冷却检查:冷却中返回true,允许触发返回false
bool is_event_in_cooldown(void)
{
if (!event_cooldown_active) {
return false;
}
// 当前时间 < 结束时间 = 还在冷却
if (esp_timer_get_time() < cooldown_end_time) {
return true;
} else {
// 冷却结束,复位
event_cooldown_active = false;
ESP_LOGI(MYTAG, "冷却时间已结束,可以再次触发驾驶事件");
return false;
}
}
// 触发一次事件后,启动50秒冷却
void start_event_cooldown(void)
{
event_cooldown_active = true;
cooldown_end_time = esp_timer_get_time() + (EVENT_COOLDOWN_MS * 1000); // 转微秒
ESP_LOGI(MYTAG, "已启动冷却,%d 毫秒内不再触发", EVENT_COOLDOWN_MS);
}
/** 诊断:因 g_ble_play_sticker_active 抑制幸运表情时打印(节流,避免刷屏) */
#define BLE_GUARD_LOG_THROTTLE_MS 3000
static uint32_t s_ble_guard_last_log_tick;
static void ble_guard_log_lucky_suppressed(const char *reason)
{
uint32_t t = lv_tick_get();
if (s_ble_guard_last_log_tick != 0 && (t - s_ble_guard_last_log_tick) < BLE_GUARD_LOG_THROTTLE_MS)
return;
s_ble_guard_last_log_tick = t;
ESP_LOGW("BLE_STICKER_GUARD",
"Lucky suppressed: %s (g_ble_play_sticker_active still true)",
reason);
}
void imu_task(void *param)
{
ESP_LOGI(MYTAG, "Starting IMU task");
driving_event_t drive_even = EVENT_NONE;
uint16_t i = 0;
/* 保持原校准逻辑不变,只调整执行时机:由主初始化路径改为后台任务执行。 */
uint32_t calib_start_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
ESP_LOGI(MYTAG, "IMU 后台校准开始");
vTaskDelay(pdMS_TO_TICKS(20));
accel_Getoffset();
noise_threshold_get();
uint32_t calib_elapsed_ms = (uint32_t)(esp_timer_get_time() / 1000ULL) - calib_start_ms;
ESP_LOGI(MYTAG, "IMU 后台校准完成,耗时: %" PRIu32 " ms", calib_elapsed_ms);
while(1)
{
// ========== 原有逻辑保持不变 ==========
mySC7_getdata(); // 永远读取传感器数据
ESP_LOGW(MYTAG,"正在读取IMU数据...");
if (current_page_idx == 0)
{
detect_driving_events();
drive_even = is_sharp_turn();
if(drive_even == EVENT_SHARP_LTURN) //急转弯判断 结束一个急转弯后判定
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("left sharp turn");
continue; /* BLE 正在播表情时不插播 IMU 幸运 */
}
start_event_cooldown(); // 启动50秒冷却
/*左急转弯反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"左急转!");
/*左急转弯反馈结束*/
}else if(drive_even == EVENT_SHARP_RTURN)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("right sharp turn");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*右急转弯反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"右急转!");
/*右急转弯反馈入口*/
}
drive_even = is_rapid_acc_or_dec();
if(drive_even==EVENT_SUDDEN_ACCEL)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("sudden accel");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*急加速反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"急加速!");
/*急加速反馈结束*/
}
else if(drive_even==EVENT_SUDDEN_BRAKE)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("sudden brake");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*急刹车反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"急刹车!");
/*急刹车反馈结束*/
}
}
// ========== 4. 通用逻辑(堵车/颠簸检测) ==========
vTaskDelay(pdMS_TO_TICKS(20));
i++;
if(i%25==0 && current_page_idx == 0)
{
//堵车数据检测
start_stop_state();
// printf("accx:%4.2f, accy:%4.2f, accz:%4.2f, gyrx:%4.2f, gyry:%4.2f, gyrz:%4.2f\n",
// ax,ay,az,gx,gy,gz);
}
if(i%200==0 && current_page_idx == 0) //2秒累加检测 反馈程序入口
{
if(is_bump()) //判断路面颠簸
{
ESP_LOGE(MYTAG,"路面颠簸!");
}
}
if(i>=15000 && current_page_idx == 0) //5分钟判定一次堵车
{
if(is_jam())
{
ESP_LOGE(MYTAG,"堵车!");
}
i = 0;
}
vTaskDelay(pdMS_TO_TICKS(30));
}
}
i2c_master_bus_handle_t get_i2c_master_bus_handle(void)
{
return s_i2c_bus;
}
i2c_master_bus_handle_t touch_bus_handle_BMI;
i2c_master_bus_handle_t touch_bus_handle_NST112;
i2c_master_bus_handle_t touch_bus_handle_MS523;
i2c_master_bus_handle_t n_i2c_bus;
//i2c_master_bus_config_t i2c_bus_config = {0};
esp_err_t BMI_init(void)
{
ESP_LOGI(TAG_IMU, "开始初始化 SC7U22...");
vTaskDelay(pdMS_TO_TICKS(100));
#if !EXAMPLE_USE_TOUCH
#error "BMI_init requires EXAMPLE_USE_TOUCH (SC7U22 shares touch I2C bus)."
#endif
//extern i2c_master_bus_handle_t touch_bus_handle; I2C主总线
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 0;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_BMI));
s_i2c_bus = touch_bus_handle_BMI;
i2c_scan_devices();
SC7U22_init(touch_bus_handle_BMI);
bmi_task_stack = heap_caps_malloc(BMI_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (bmi_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate BMI task stack");
return ESP_FAIL;
}
TaskHandle_t task_handle = xTaskCreateStaticPinnedToCore(imu_task, "BMI", BMI_TASK_STACK_SIZE, NULL, BMI_TASK_PRIORITY, bmi_task_stack, &bmi_task_tcb, 0);
if (task_handle == NULL)
{
ESP_LOGE("LVGL", "Failed to create BMI task");
heap_caps_free(bmi_task_stack);
bmi_task_stack = NULL;
return ESP_FAIL;
}
ESP_LOGI("LVGL", "BMI task created successfully");
return ESP_OK;
}
i2c_master_dev_handle_t NST112_handle;
uint8_t temp_data[2] ={0};
uint8_t config_data[2] = {0};
uint8_t H_tmp_data[2] = {0};
uint8_t L_tmp_data[2] = {0};
esp_err_t NST112_Reg_W(uint8_t reg_addr, uint8_t data)
{
uint8_t write_buf[] = {reg_addr, data};
return i2c_master_transmit(NST112_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
esp_err_t NST112_Reg_R(uint8_t reg_addr)
{
esp_err_t ret = i2c_master_transmit_receive(NST112_handle,&reg_addr,1,temp_data,2,1000 / portTICK_PERIOD_MS);
if(ret != ESP_OK){
ESP_LOGE("NST112", "读取 NST112 寄存器失败,错误码: %s", esp_err_to_name(ret));
return ret;
}
else{
return ESP_OK;
}
}
extern StaticTask_t nst112_task_tcb;
extern StackType_t *nst112_task_stack;
void nst112_task(void *param)
{
ESP_LOGI("NST112", "Starting NST112 task");
uint8_t read_addr = 0x00;
float temp_c = 0;
while(1)
{
i2c_master_transmit_receive(NST112_handle, &read_addr, 1, temp_data, 2, 1000 / portTICK_PERIOD_MS);
int16_t raw = (int16_t)((temp_data[0] << 8) | temp_data[1]);
temp_c = raw / 256.0f;
ESP_LOGI("NST112", "TEMP: %02X %02X", temp_data[0], temp_data[1]);
ESP_LOGI("NST112", "TEMP (C): %f", temp_c);
if(temp_c > 70){
ESP_LOGI("NST112", "TEMP: WARNING");
}
if(temp_c < (-20)){
ESP_LOGI("NST112", "TEMP: WARNING");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
esp_err_t NST112_init(void)
{
//extern i2c_master_bus_handle_t touch_bus_handle_touch;
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 1;
i2c_bus_config.scl_io_num = NST112_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = NST112_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_NST112)); //此处单传感器调试用,正式使用时改为传入共享总线句柄
n_i2c_bus = touch_bus_handle_NST112;//touch_bus_handle_touch
s_i2c_bus = touch_bus_handle_NST112;
i2c_scan_devices();
ESP_LOGI("NST112", "NST112 on shared I2C bus: %p", (void *)n_i2c_bus);
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = NST112_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(n_i2c_bus, &dev_cfg, &NST112_handle);
if (ret != ESP_OK) {
ESP_LOGE("NST112", "添加 NST112 设备失败");
return ret;
}
uint8_t write_buf[] = {0x01,0x60,0xA0}; // 0x01 寄存器地址,0x60 和 0xA0 是要写入的数据
uint8_t high_temp_config_reg[] = {0x03,0x05,0x50};
uint8_t low_temp_config_reg[] = {0x02,0x00,0xA0};
uint8_t read_config_addr = 0x01;
uint8_t read_H_TMP_addr = 0x02;
uint8_t read_L_TMP_addr = 0x03;
i2c_master_transmit(NST112_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_config_addr,1 ,config_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_L_TMP_addr,1 ,H_tmp_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_H_TMP_addr,1 ,L_tmp_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI("NST112", "TEMP: %02X %02X L:%02X %02X H:%02X %02X ", config_data[0], config_data[1],L_tmp_data[0],L_tmp_data[1],H_tmp_data[0],L_tmp_data[1]);
//i2c_master_transmit(NST112_handle, high_temp_config_reg, sizeof(high_temp_config_reg), 1000 / portTICK_PERIOD_MS);
//i2c_master_transmit(NST112_handle, low_temp_config_reg, sizeof(low_temp_config_reg), 1000 / portTICK_PERIOD_MS);
vTaskDelay(pdMS_TO_TICKS(100));
uint8_t read_addr = 0x00;
//i2c_master_transmit_receive(NST112_handle, &read_addr, 1, temp_data, 2, 1000 / portTICK_PERIOD_MS);
nst112_task_stack = heap_caps_malloc(NST112_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (nst112_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate NST112 task stack");
return ESP_FAIL;
}
xTaskCreateStaticPinnedToCore(nst112_task, "NST112", NST112_TASK_STACK_SIZE, NULL, NST112_TASK_PRIORITY, nst112_task_stack, &nst112_task_tcb,0);
return ESP_OK;
}
#define MS523_CTRL_GPIO GPIO_NUM_48
i2c_master_dev_handle_t MS523_handle;
extern StaticTask_t MS523_task_tcb;
extern StackType_t *MS523_task_stack;
esp_err_t ms523_write_byte(uint8_t reg_addr, uint8_t data) {
uint8_t write_buf[2] = {reg_addr, data};
esp_err_t ret = i2c_master_transmit(MS523_handle, write_buf, sizeof(write_buf), pdMS_TO_TICKS(100));
if (ret != ESP_OK) {
ESP_LOGE("MS523", "Write reg 0x%02X failed: %s", reg_addr, esp_err_to_name(ret));
}
return ret;
}
esp_err_t ms523_read_byte(uint8_t reg_addr, uint8_t *data) {
esp_err_t ret = i2c_master_transmit_receive(MS523_handle, &reg_addr, 1, data, 1, pdMS_TO_TICKS(100));
if (ret == ESP_OK) {
ESP_LOGI("MS523", "Read reg 0x%02X = 0x%02X", reg_addr, *data);
} else {
ESP_LOGE("MS523", "Read reg 0x%02X failed: %s", reg_addr, esp_err_to_name(ret));
}
return ret;
}
#define GPIO_OUTPUT_PIN GPIO_NUM_48 // 以GPIO2为例
void MS523_ctrl_init(void){
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_OUTPUT_PIN), // 选择要配置的引脚[reference:5]
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式[reference:6]
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用内部上拉电阻[reference:7]
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用内部下拉电阻[reference:8]
.intr_type = GPIO_INTR_DISABLE // 禁用中断[reference:9]
};
gpio_config(&io_conf); // 应用配置[reference:10]
// 2. 设置GPIO输出高电平
gpio_set_level(GPIO_OUTPUT_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI("MS523", "当前输出高电平");
}
void ms523_detect_card(void) {
// 1-4:发送寻卡指令
ms523_write_byte(0x0A, 0x80);
ms523_write_byte(0x09, 0x52);
ms523_write_byte(0x0D, 0x87);
ms523_write_byte(0x01, 0x0C);
// 5:轮询等待 RxIRq(超时 50ms)
uint8_t irq = 0;
int timeout_ms = 50;
TickType_t start = xTaskGetTickCount();
while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(timeout_ms)) {
ms523_read_byte(0x04, &irq);
if (irq & 0x20) {
ESP_LOGI("MS523", "✅ Card detected!");
return; // 检测到卡,退出函数
}
vTaskDelay(pdMS_TO_TICKS(1));
}
// 检查碰撞
uint8_t err;
ms523_read_byte(0x06, &err);
if (err & 0x08) {
ESP_LOGI("MS523", "✅ Card detected (Collision)!");
return;
}
// 无卡
ESP_LOGW("MS523", "No card detected.");
}
void MS523_task(void *param){
while (1)
{
ms523_detect_card();
ESP_LOGW("MS523", "运行中");
vTaskDelay(500);
}
}
esp_err_t MS523_init(void)
{
MS523_ctrl_init();
ESP_LOGI(TAG_IMU, "开始初始化 MS523");
vTaskDelay(pdMS_TO_TICKS(100));
#if !EXAMPLE_USE_TOUCH
#error "BMI_init requires EXAMPLE_USE_TOUCH (SC7U22 shares touch I2C bus)."
#endif
//extern i2c_master_bus_handle_t touch_bus_handle; I2C主总线
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 0;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_MS523));
s_i2c_bus = touch_bus_handle_MS523;
//i2c_scan_devices();
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = MS523_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &MS523_handle);
uint8_t reg_addr = 0x37;
uint8_t version = 0;
uint8_t data = 0;
ms523_read_byte(reg_addr,&version);
ms523_write_byte(0x14, 0x83); // 开启 TX1/TX2 天线驱动
ms523_write_byte(0x12, 0x00); // 发送速率 106k
ms523_write_byte(0x13, 0x08); // 接收速率 106k
ms523_write_byte(0x26, 0x48); // 接收增益 48dB
if (ret == ESP_OK) {
ESP_LOGI("MS523", "NFC version:0x%02X", version);
} else {
ESP_LOGE("MS523", "Read version failed: %s", esp_err_to_name(ret));
}
MS523_task_stack = heap_caps_malloc(MS523_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (MS523_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate NST112 task stack");
return ESP_FAIL;
}
//ms523_write_byte(0x0A, 0x80);
//ms523_read_byte(0x0A,&data);
//ms523_write_byte(0x09, 0x52);
//ms523_read_byte(0x09,&data);
//ms523_write_byte(0x0D, 0x87);
//ms523_read_byte(0x0D,&data);
//ms523_write_byte(0x01, 0x0C);
//ms523_read_byte(0x01,&data);
xTaskCreateStaticPinnedToCore(MS523_task, "MS523", MS523_TASK_STACK_SIZE, NULL, MS523_TASK_PRIORITY, MS523_task_stack, &MS523_task_tcb,1);
return ESP_OK;
}
+130
View File
@@ -0,0 +1,130 @@
#include "driver/i2c_master.h"
#ifndef __SC7U22_H_
#define __SC7U22_H_
#define SC7U22_CONFIG 0x1A
#define SC7U22_GYRO_CONFIG 0x42
#define SC7U22_ACCEL_CONFIG 0x40
#define SC7U22_ACCEL_XOUT_H 0x0C
#define SC7U22_ACCEL_XOUT_L 0x0D
#define SC7U22_ACCEL_YOUT_H 0x0E
#define SC7U22_ACCEL_YOUT_L 0x0F
#define SC7U22_ACCEL_ZOUT_H 0x10
#define SC7U22_ACCEL_ZOUT_L 0x11
#define SC7U22_GYRO_XOUT_H 0x12
#define SC7U22_GYRO_XOUT_L 0x13
#define SC7U22_GYRO_YOUT_H 0x14
#define SC7U22_GYRO_YOUT_L 0x15
#define SC7U22_GYRO_ZOUT_H 0x16
#define SC7U22_GYRO_ZOUT_L 0x17
#define SC7U22_WHO_AM_I 0x01
#define SC7U22_I2C_ADDR 0x18
#define NST112_I2C_ADDR 0x48
#define MS523_I2C_ADDR 0x28
#define I2C_MASTER_FREQ_HZ 800000 // kHz 800000
#define I2C_MASTER_FREQ_TEMP_HZ 100000 // kHz 800000
#define GRAVITY_EARTH (9.80665f)
#define ALPHA 0.90f // 高通系数
#define BUM_THRESHOLD 4.0e8f // 颠簸能量阈值(暂定)
#define turn_gyro_threshold 30.0f //急转弯角速度阈值
#define turn_accel_threshold 2.0f //急转弯向心加速度阈值
#define rapid_accel_threshold 3.0f //急加速阈值
#define rapid_decel_threshold -3.0f //急刹车阈值
#define DATA_BUFFER_SIZE 200
#define is_stop_magnitude 0.35f //启动
#define stop_proportion_threshold 0.5f //停止时间比例阈值
#define start_stop_threshold 15 //启停次数阈值
// 驾驶事件冷却屏蔽(10秒内不再触发表情)
#define EVENT_COOLDOWN_MS 10000 // 冷却时间 10 秒
extern float ax,ay,az,gx,gy,gz;
typedef struct
{
int16_t Ax;
int16_t Ay;
int16_t Az;
int16_t Gx;
int16_t Gy;
int16_t Gz;
}SC7U22_DataType;
struct acc_offset
{
float acc_x_offset;
float acc_y_offset;
float acc_z_offset;
float gyr_x_offset;
float gyr_y_offset;
float gyr_z_offset;
};
typedef struct{
float accx_noise;
float accy_noise;
float accz_noise;
float gyrx_noise;
float gyry_noise;
float gyrz_noise;
}noise_datatype;
typedef struct {
float accel_x;
float accel_y;
float accel_z;
float gyro_x;
float gyro_y;
float gyro_z;
int64_t timestamp;
} sensor_data_t;
typedef enum {
EVENT_NONE = 0,
EVENT_SUDDEN_ACCEL, // 急加速
EVENT_SUDDEN_BRAKE, // 急刹车
EVENT_SHARP_LTURN, //急左转弯
EVENT_SHARP_RTURN, //急右转弯
} driving_event_t;
void SC7U22_init(i2c_master_bus_handle_t i2c_bus);
esp_err_t SC7U22_Reg_W(uint8_t reg_addr, uint8_t data);
esp_err_t SC7U22_Reg_R(uint8_t reg_addr, uint8_t *data);
// void SC7U22_DataGet(SC7U22_DataType* SC7U22_DataStucture);
void mySC7_getdata(void); //获得处理后的数据
void SC7U22_Sensor_enable(void);
void noise_threshold_get(void);
void accel_Getoffset(void);
float lsb_to_mps2(int16_t val, float g_range, uint8_t bit_width);
float lsb_to_dps(int16_t val, float dps, uint8_t bit_width);
void bum_energy_get(void);
bool is_bump(void);
void sharp_turn_detect(void);
driving_event_t is_sharp_turn(void);
void start_stop_state(void);
void detect_driving_events(void);
driving_event_t is_rapid_acc_or_dec(void);
bool is_jam(void);
void imu_task(void *param);
i2c_master_bus_handle_t get_i2c_master_bus_handle(void);
esp_err_t BMI_init(void);
esp_err_t NST112_init(void);
esp_err_t MS523_init(void);
#endif
+171
View File
@@ -0,0 +1,171 @@
/*****************************************************include files **************************************************************************/
#include "screen_brightness.h"
#include "LVGL/lvgl_init.h"
#include "ui.h"
/******************************************************* variable declaration ************************************************************************/
/*
* 亮度模型:UI/BLE 的 0~100% 线性映射到面板 PWM [BRIGHTNESS_PWM_MIN, BRIGHTNESS_PWM_MAX]。
* 选 55 作为下限:约 21.5% 全量程,且 (255-55)=200,duty = 55 + percent*2,0~100 与整数 PWM 一一对应、无除法舍入误差。
* 若硬件需其它暗场门限,只改 BRIGHTNESS_PWM_MIN(需保持 SPAN 与映射公式一致或同步改除法)。
*/
#define BRIGHTNESS_PWM_MIN 55
#define BRIGHTNESS_PWM_MAX 255
#define BRIGHTNESS_PWM_SPAN (BRIGHTNESS_PWM_MAX - BRIGHTNESS_PWM_MIN)
// 当前面板 PWM(BRIGHTNESS_PWM_MIN~BRIGHTNESS_PWM_MAX);未读 NVS 前取中间偏亮,避免明显过暗
uint8_t g_current_brightness = (uint8_t)(BRIGHTNESS_PWM_MIN + BRIGHTNESS_PWM_SPAN * 3 / 4);
static bool g_brightness_dirty = false;
static uint8_t g_brightness_commit_pending = 0;
static TimerHandle_t g_brightness_commit_timer = NULL;
/******************************************************* functions declaration ************************************************************************/
void save_brightness_to_nvs(uint8_t value); /* forward declaration for timer callback */
void commit_brightness_if_dirty(void);
extern esp_err_t panel_qspi_amoled_set_brightness(esp_lcd_panel_handle_t panel, uint8_t brightness);
/******************************************************* functions definition ************************************************************************/
// 保存亮度到 NVS
void save_brightness_to_nvs(uint8_t value)
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READWRITE, &my_handle);
if (err == ESP_OK)
{
nvs_set_u8(my_handle, "brightness", value);
nvs_commit(my_handle);
nvs_close(my_handle);
// ESP_LOGI("NVS", "亮度已保存: %d", value);
}
}
static void brightness_commit_timer_cb(TimerHandle_t xTimer)
{
(void)xTimer;
save_brightness_to_nvs(g_brightness_commit_pending);
}
// 从 NVS 读取亮度
void load_brightness_from_nvs(void)
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READONLY, &my_handle);
if (err == ESP_OK)
{
uint8_t saved_val = 0;
if (nvs_get_u8(my_handle, "brightness", &saved_val) == ESP_OK)
{
g_current_brightness = saved_val;
if (g_current_brightness < BRIGHTNESS_PWM_MIN)
g_current_brightness = BRIGHTNESS_PWM_MIN;
if (g_current_brightness > BRIGHTNESS_PWM_MAX)
g_current_brightness = BRIGHTNESS_PWM_MAX;
ESP_LOGI("NVS", "加载保存的亮度: %d", g_current_brightness);
}
nvs_close(my_handle);
}
}
// ================= 对外接口 =================
// 设置亮度 (供 UI 和 BLE 调用)
// 参数 percent: 0-100,0 对应 PWM_MIN、100 对应 PWM_MAX
void set_system_brightness(int percent)
{
if (percent < 0)
percent = 0;
if (percent > 100)
percent = 100;
uint8_t duty = (uint8_t)(BRIGHTNESS_PWM_MIN + ((unsigned)percent * BRIGHTNESS_PWM_SPAN) / 100U);
// 3. 应用到硬件
if (panel_handle)
{
panel_qspi_amoled_set_brightness(panel_handle, duty);
}
// 4. 更新全局变量并保存
if (g_current_brightness != duty)
{
g_current_brightness = duty;
g_brightness_dirty = true;
request_brightness_slider_sync();
}
/* BLE/UI 共用:有变更时经定时器落盘 NVS(避免在回调里同步写) */
commit_brightness_if_dirty();
}
/* 退出亮度界面时调用:只写一次 NVS,避免频繁保存导致不稳定 */
void commit_brightness_if_dirty(void)
{
if (!g_brightness_dirty)
return;
g_brightness_commit_pending = g_current_brightness;
g_brightness_dirty = false;
/* NVS 写放到 timer 任务上下文,避免在 LVGL 点击事件回调里同步写导致崩溃 */
if (g_brightness_commit_timer == NULL)
{
g_brightness_commit_timer = xTimerCreate("brightness_commit_tmr",
pdMS_TO_TICKS(50),
pdFALSE,
NULL,
brightness_commit_timer_cb);
}
if (g_brightness_commit_timer)
{
xTimerStop(g_brightness_commit_timer, 0);
if (xTimerStart(g_brightness_commit_timer, 0) != pdPASS)
{
/* 定时器队列满等异常时仍落盘,避免已清 dirty 却永不写入 NVS */
save_brightness_to_nvs(g_brightness_commit_pending);
}
}
else
{
/* xTimerCreate 失败(如堆不足)时直接写 NVS,否则与蓝牙路径行为不一致 */
save_brightness_to_nvs(g_brightness_commit_pending);
}
}
// 获取当前亮度百分比 (供 UI 初始化滑块与 BLE 查询,0~100)
int get_system_brightness_percent(void)
{
unsigned d = g_current_brightness;
if (d <= BRIGHTNESS_PWM_MIN)
return 0;
if (d >= BRIGHTNESS_PWM_MAX)
return 100;
return (int)((d - BRIGHTNESS_PWM_MIN) * 100U / BRIGHTNESS_PWM_SPAN);
}
// 【核心函数 2】设置是否阻止休眠 (例如 OTA 时调用 set_block_sleep(true))
void set_block_auto_sleep(bool block)
{
block_auto_sleep = block;
if (block)
{
system_keep_alive(); // 阻止时顺便刷新一下时间
}
}
/* 亮度滑动条同步:将设置页滑动条与当前系统亮度对齐 */
void request_brightness_slider_sync(void)
{
extern lv_ui guider_ui;
extern int get_system_brightness_percent(void);
int percent = get_system_brightness_percent();
if (guider_ui.screen_set_slider_light != NULL &&
lv_obj_is_valid(guider_ui.screen_set_slider_light)) {
lv_slider_set_value(guider_ui.screen_set_slider_light, percent, LV_ANIM_OFF);
}
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef __SCREEN_BRIGHTNESS_H__
#define __SCREEN_BRIGHTNESS_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern uint8_t g_current_brightness;
/** percent: 0~100,与滑动条/BLE 一致;0→PWM 下限、100→PWM 上限(见 screen_brightness.c) */
void set_system_brightness(int percent);
/** 有亮度脏标记时写 NVS(内部 50ms 防抖定时器);供退出设置页等场景也可直接调用 */
void commit_brightness_if_dirty(void);
#ifdef __cplusplus
}
#endif
#endif
+237
View File
@@ -0,0 +1,237 @@
#include "screen_init.h"
#include "config.h"
#include "LVGL/lvgl_init.h"
#include "driver/i2c_master.h"
const char *TAG_LVGL = "lvgl_init";//打印标签
#if EXAMPLE_USE_TOUCH
esp_lcd_touch_handle_t tp = NULL;
bool is_anim_paused = false;
i2c_master_bus_handle_t touch_bus_handle_touch; // 定义触摸屏I2C总线句柄(全局,供BMI270共享)
#endif
static void read_lcd_id(esp_lcd_panel_io_handle_t read_io)
{
uint8_t id_buf[3] = {0};
esp_err_t ret = esp_lcd_panel_io_rx_param(read_io, 0x03000400, id_buf, 3);
if (ret == ESP_OK) {
ESP_LOGI(TAG_LVGL, "LCD ID: %02X %02X %02X", id_buf[0], id_buf[1], id_buf[2]);
} else {
ESP_LOGE(TAG_LVGL, "Read ID failed: %s", esp_err_to_name(ret));
}
uint8_t id1 = 0;
uint8_t id2 = 0;
uint8_t id3 = 0;
esp_err_t ret1 = esp_lcd_panel_io_rx_param(read_io, 0x0300DA00, &id1, 1);
esp_err_t ret2 = esp_lcd_panel_io_rx_param(read_io, 0x0300DB00, &id2, 1);
esp_err_t ret3 = esp_lcd_panel_io_rx_param(read_io, 0x0300DC00, &id3, 1);
if (ret1 == ESP_OK && ret2 == ESP_OK && ret3 == ESP_OK) {
ESP_LOGI(TAG_LVGL, "LCD RDID: %02X %02X %02X", id1, id2, id3);
} else {
ESP_LOGE(TAG_LVGL, "Read RDID failed: %s %s %s",
esp_err_to_name(ret1), esp_err_to_name(ret2), esp_err_to_name(ret3));
}
}
/*
const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[13] = {
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xC4, (uint8_t[]){0x80}, 1, 0},
{0x35, (uint8_t[]){0x00}, 1, 0},
{0x3A, (uint8_t[]){0x77}, 1, 0},
{0x53, (uint8_t[]){0x20}, 1, 0},
{0x51, (uint8_t[]){0x00}, 1, 0},
{0x63, (uint8_t[]){0xFF}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x16, 0x01, 0xAF}, 4, 0},
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xF5}, 4, 0},
{0x11, (uint8_t[]){0x00}, 0, 120},
{0x29, (uint8_t[]){0x00}, 0, 60},
{0x58, (uint8_t[]){0x07}, 1, 10},
{0x36, (uint8_t[]){0xA8}, 1, 0},
};
*/
const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[] = {
// 0x11: 退出睡眠模式(Sleep Out),之后延时 60ms
{0x11, (uint8_t[]){0x00}, 0, 60},
// 0x2A: 列地址范围 0~239(0x00 0x00 ~ 0x00 0xEF)
{0x2A, (uint8_t[]){0x00, 0x00, 0x00, 0xEF}, 4, 0},
// 0x2B: 行地址范围 0~295(0x00 0x00 ~ 0x01 0x27)
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x27}, 4, 0},
// 0x44: 撕裂效应行(TE Line)设为 295
{0x44, (uint8_t[]){0x01, 0x27}, 2, 0},
// 0x3A: 像素格式(Pixel Format)0x55(通常为 16bit/18bit)
{0x3A, (uint8_t[]){0x55}, 1, 0},
// 0x35: TE 信号控制,0x00 = 关闭
{0x35, (uint8_t[]){0x00}, 1, 0},
// 0x53: 亮度控制寄存器,设为 0x20
{0x53, (uint8_t[]){0x20}, 1, 0},
// 0x51: 亮度值设为最高 0xFF,之后延时 60ms 再开启显示
{0x51, (uint8_t[]){0xFF}, 1, 60},
// 0x29: 开启显示(Display On),无需额外延时
{0x29, (uint8_t[]){0x00}, 0, 0},
};
// QSPI AMOLED供应商配置
qspi_amoled_vendor_config_t vendor_config =
{
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
.flags =
{
.use_qspi_interface = 1,
},
};
// LCD面板配置
const esp_lcd_panel_dev_config_t panel_config =
{
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
extern esp_lcd_panel_io_handle_t io_handle;
void read_co5300_response(void) {
}
esp_err_t app_lcd_init(void)//QSPI初始化
{
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG_LVGL, "Initialize QSPI bus");
// 使用原始的SPI总线最大传输大小,确保稳定传输
const spi_bus_config_t buscfg = QSPI_AMOLED_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK,
EXAMPLE_PIN_NUM_LCD_DATA0,
EXAMPLE_PIN_NUM_LCD_DATA1,
EXAMPLE_PIN_NUM_LCD_DATA2,
EXAMPLE_PIN_NUM_LCD_DATA3,
(EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUF_HEIGHT * LCD_BIT_PER_PIXEL / 8) + 64);
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG_LVGL, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
.dc_gpio_num = -1,
.spi_mode = AMOLED_QSPI_MODE,
.pclk_hz = AMOLED_QSPI_MAX_PCLK,
.trans_queue_depth = 3,
.on_color_trans_done = example_notify_lvgl_flush_ready,
.user_ctx = NULL,
.lcd_cmd_bits = 32,
.lcd_param_bits = 8,
.flags = {
.quad_mode = true,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
ESP_LOGI(TAG_LVGL, "Install QSPI_AMOLED panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_qspi_amoled(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, EXAMPLE_LCD_X_GAP, EXAMPLE_LCD_Y_GAP));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
/* 初始化时设置亮度为0,避免白光闪烁,等待 UI 加载后渐变亮屏 */
esp_err_t br = panel_qspi_amoled_set_brightness(panel_handle,100);
//uint8_t read_buf[3] = {0}; // RDDID 返回 3 个字节
/*
// 发送读指令 0x04,读取 3 个字节的回应数据
esp_err_t ret2 = esp_lcd_panel_io_rx_param(io_handle, 0x04, read_buf, 3);
if (ret2 == ESP_OK) {
ESP_LOGI("TAG", "读取回应成功!ID1: 0x%02X, ID2: 0x%02X, ID3: 0x%02X",
read_buf[0], read_buf[1], read_buf[2]);
// 根据数据手册,正常回应通常为:33h, 10h, 00h 或 33h, 11h, 00h
} else {
ESP_LOGE("TAG", "读取回应失败:%s", esp_err_to_name(ret2));
}*/
//read_lcd_id(io_handle);
//ESP_LOGI(TAG_LVGL,"br=%d",br);
//uint8_t blue_color[] = {0x00, 0x00, 0xFF};
//esp_err_t brt =panel_qspi_amoled_draw_bitmap(panel_handle, 0, 0, EXAMPLE_LCD_H_RES - 1, EXAMPLE_LCD_V_RES - 1, blue_color);
/*
if (br != ESP_OK)
{
ESP_LOGE(TAG_LVGL, "panel_qspi_amoled_set_brightness failed: %s", esp_err_to_name(br));
}
else
{
ESP_LOGI(TAG_LVGL, "Screen brightness set to 0 (等待 UI 加载后渐变亮屏)");
}
*/
/* Register done callback */
//ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, disp));
return ret;
}
esp_err_t app_touch_init(void)
{
ESP_LOGI(TAG_LVGL, "初始化 I2C 总线 (新驱动模式)");
// 1. 配置 I2C 总线
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = TOUCH_HOST,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_touch));
// 2. 配置触摸面板 IO(这里会自动适配新驱动)
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = {
.dev_addr = 0x15, // CST816/CST820 的默认地址,根据实际修改
.on_color_trans_done = NULL,
.user_ctx = NULL,
.control_phase_bytes = 1,
.dc_bit_offset = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.scl_speed_hz = 400 * 1000,
.flags = {
.disable_control_phase = 1,
}};
// 使用新总线句柄创建 IO
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(touch_bus_handle_touch, &tp_io_config, &tp_io_handle));
// 3. 配置触摸芯片参数
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 1,
.mirror_x = 0,
.mirror_y = 1,
},
};
ESP_LOGI(TAG_LVGL, "创建触摸控制器句柄");
return esp_lcd_touch_new_i2c(tp_io_handle, &tp_cfg, &tp);
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef __SCREEN_INIT_H__
#define __SCREEN_INIT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern const esp_lcd_panel_dev_config_t panel_config;
extern const char *TAG_LVGL ;
esp_err_t app_lcd_init(void);
#ifdef __cplusplus
}
#endif
#endif
+177
View File
@@ -0,0 +1,177 @@
/*****************************************************include files **************************************************************************/
#include "screen_sleep.h"
#include "ui/generated/events_init.h"
#include "ui/ui.h"
#define AUTO_SLEEP_DEFAULT_MS 0//默认休眠时间
/******************************************************* variable declaration ************************************************************************/
static uint32_t g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
// 初始化为当前开机时间
int64_t last_activity_timestamp = 0; //最后一次用户活动的时间戳
static bool g_sleep_timeout_dirty = false; //休眠时间是否被改过但是没有写入NVS
static uint32_t g_sleep_commit_pending_ms = 0; //待提交到NVS的休眠时间缓存值
/* ===== 休眠设置 NVS 提交:离开休眠界面时写一次 ===== */
static TimerHandle_t g_sleep_commit_timer = NULL; //创建了freeRTOS的软件定时器句柄
extern volatile bool is_system_sleeping; //系统睡眠标记
extern bool block_auto_sleep ; //是否阻止自动休眠
extern int current_page_idx ; //当前页面索引
/******************************************************* functions declaration ************************************************************************/
static void save_timeout_to_nvs(uint32_t ms);
static void sleep_commit_timer_cb(TimerHandle_t xTimer);
/******************************************************* functions definition ************************************************************************/
// 【新增】从 NVS 加载休眠时间
void load_timeout_from_nvs(void)//从nvs加载休眠时间
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READONLY, &my_handle);
if (err != ESP_OK)
{
ESP_LOGW("NVS", "打开 system_cfg 失败: %s,休眠时间使用默认 %lu ms",
esp_err_to_name(err), (unsigned long)AUTO_SLEEP_DEFAULT_MS);
g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
system_keep_alive();
return;
}
uint32_t saved_val = 0;
if (nvs_get_u32(my_handle, "sleep_ms", &saved_val) == ESP_OK)
{
g_auto_sleep_timeout_ms = saved_val;
ESP_LOGI("NVS", "从 NVS 加载休眠时间: %lu ms", (unsigned long)g_auto_sleep_timeout_ms);
}
else
{
g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
ESP_LOGW("NVS", "未找到 sleep_ms,使用默认 %lu ms", (unsigned long)AUTO_SLEEP_DEFAULT_MS);
}
nvs_close(my_handle);
/* 以完成 NVS 加载时刻为空闲计时起点,避免 last_activity 长期为 0 误判超时 */
system_keep_alive();
}
// 【新增】保存设置到 NVS
static void save_timeout_to_nvs(uint32_t ms)//保存休眠时间到nvs
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READWRITE, &my_handle);
if (err == ESP_OK)
{
nvs_set_u32(my_handle, "sleep_ms", ms);
nvs_commit(my_handle); // 提交保存
nvs_close(my_handle);
ESP_LOGI("NVS", "休眠时间已保存: %lu ms", (unsigned long)ms);
}
else
{
ESP_LOGE("NVS", "无法打开 NVS 保存设置");
}
}
void persist_auto_sleep_timeout_ms_now(uint32_t ms)
{
if (g_sleep_commit_timer != NULL)
{
xTimerStop(g_sleep_commit_timer, pdMS_TO_TICKS(100));
}
g_auto_sleep_timeout_ms = ms;
g_sleep_timeout_dirty = false;
g_sleep_commit_pending_ms = ms;
system_keep_alive();
save_timeout_to_nvs(ms);
}
// 2. 【新增】设置超时时间的函数 (提供给按钮调用)
// 输入 0 代表永不休眠 设置自动休眠时间
void set_auto_sleep_timeout(uint32_t ms)
{
if (g_auto_sleep_timeout_ms == ms) {
return;
}
g_auto_sleep_timeout_ms = ms;
system_keep_alive();
ESP_LOGI("PM", "休眠设置更新: %lu ms (mark dirty)", (unsigned long)ms);
g_sleep_timeout_dirty = true;
}
// 【核心函数 1】系统保活:任何操作都应该调用这个函数
void system_keep_alive(void)//更新当前的活动时间戳
{
last_activity_timestamp = esp_timer_get_time() / 1000; // 更新为当前时间(ms)
}
//获取自动休眠时间
uint32_t get_auto_sleep_timeout(void)
{
return g_auto_sleep_timeout_ms;
}
//如果脏标记被设置过之后 发送自动休眠时间
void commit_auto_sleep_timeout_if_dirty(void)
{
if (!g_sleep_timeout_dirty)
return;
g_sleep_commit_pending_ms = g_auto_sleep_timeout_ms;
g_sleep_timeout_dirty = false;
/* 真正的 NVS 写放到 timer 回调里,避免在 LVGL 事件栈上下文里写 NVS */
if (g_sleep_commit_timer == NULL)
{
g_sleep_commit_timer = xTimerCreate("sleep_commit_tmr",
pdMS_TO_TICKS(50),
pdFALSE,
NULL,
sleep_commit_timer_cb);
}
if (g_sleep_commit_timer)
{
if (xTimerStart(g_sleep_commit_timer, 0) != pdPASS)
{
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
}
else
{
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
}
static void sleep_commit_timer_cb(TimerHandle_t xTimer)
{
(void)xTimer;
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
void auto_sleep_checker_cb(lv_timer_t *timer)
{
// 如果已经在睡觉,或者被阻止,或者设置为 0 (永不),直接返回
if (is_system_sleeping || block_auto_sleep || g_auto_sleep_timeout_ms == 0)
{
return;
}
int64_t now = esp_timer_get_time() / 1000;
// 使用变量 g_auto_sleep_timeout_ms 替代原来的 30000
if ((now - last_activity_timestamp) > g_auto_sleep_timeout_ms)
{
ESP_LOGW("PM", "系统空闲超时,自动进入休眠...");
if (current_page_idx != 0)
{
ui_change_page(0, LV_SCR_LOAD_ANIM_FADE_ON);
}
toggle_sleep_mode(true);
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __SCREEN_SLEEP_H__
#define __SCREEN_SLEEP_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern const esp_lcd_panel_dev_config_t panel_config;
extern const char *TAG_LVGL ;
esp_err_t app_lcd_init(void);
void system_keep_alive(void);
void load_timeout_from_nvs(void);
void set_auto_sleep_timeout(uint32_t ms);
uint32_t get_auto_sleep_timeout(void);
void commit_auto_sleep_timeout_if_dirty(void);
/** 立即把 ms 写入 RAM 与 NVS(停掉延迟提交定时器);用于恢复出厂等须在 esp_restart 前落盘的场景 */
void persist_auto_sleep_timeout_ms_now(uint32_t ms);
void auto_sleep_checker_cb(lv_timer_t *timer);
#ifdef __cplusplus
}
#endif
#endif
+473
View File
@@ -0,0 +1,473 @@
#include "speech_app.h"
#include "speech_init.h"
#include "LVGL/lvgl_init.h"
#include <math.h>
static const char *TAG_LVGL = "sr";
// 音频设备句柄
esp_codec_dev_handle_t record_dev = NULL;
const char *cmd_phoneme[] =
{
"ni hao xiao qi",
"xiao qi zai jian", // 小奇,再见
"xiao qi tan ge zi ta", // 小奇,弹个吉他
"xiao qi bi ge xin", // 小奇,比个心
"xiao qi si kao yi xia zhe ge wen ti", // 小奇,思考一下这个问题
};
const uint32_t cmd_phoneme_number = sizeof(cmd_phoneme)/sizeof(cmd_phoneme[0]);
typedef struct __attribute__((packed)) {
char chunkID[4];
uint32_t chunkSize;
char format[4];
char subchunk1ID[4];
uint32_t subchunk1Size;
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
char subchunk2ID[4];
uint32_t subchunk2Size;
} wav_header_t;
void write_wav_header(FILE* fp, uint32_t total_data_bytes) {
wav_header_t wav = {
.chunkID = {'R','I','F','F'},
.format = {'W','A','V','E'},
.subchunk1ID = {'f','m','t',' '},
.subchunk1Size = 16,
.audioFormat = 1,
.numChannels = 2, // 你的参数是resdata
.sampleRate = 16000, // 你的参数是16kHz
.bitsPerSample = 16, // 你的参数是16-bit
.subchunk2ID = {'d','a','t','a'},
.subchunk2Size = total_data_bytes,
};
// 计算其他依赖的字段
wav.byteRate = wav.sampleRate * wav.numChannels * wav.bitsPerSample / 8;
wav.blockAlign = wav.numChannels * wav.bitsPerSample / 8;
wav.chunkSize = 36 + wav.subchunk2Size; // 文件总大小 - 8
// 写入文件头
fwrite(&wav, sizeof(wav), 1, fp);
}
int slience_count = 0;
#define GAIN 1.5f // 例如增益 1.5(约 +3.5 dB)
#define MAX_VAL 32767
#define MIN_VAL -32768
/**
* @brief 音频数据采集任务
* 从I2S接口读取音频数据并喂给音频前端处理
*
* @param arg 音频前端数据句柄
*/
void feed_Task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting feed task");
esp_afe_sr_data_t *afe_data_ = arg;
// 获取音频前端处理的块大小和通道数
/*
int feed_chunksize = afe_handle->get_feed_chunksize(afe_data_);
int feed_nch = afe_handle->get_feed_channel_num(afe_data_);
ESP_LOGE(TAG_LVGL, "feed_chunksize: %d, feed_nch: %d", feed_chunksize, feed_nch);
*/
/*
FILE* fp = fopen("/extflash/audio1.wav", "wb"); // 或 "/extflash/audio.wav"
if (!fp) {
ESP_LOGW(TAG_MAIN, "WAV文件打开失败,无法写入");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 2;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 2; // 2秒总采样数(每声道)
uint32_t totalDataBytes = totalSamples * numChannels * (bitsPerSample / 8);
write_wav_header(fp, totalDataBytes);
int counter = 0;
*/
while (1)
{
size_t bytesIn = 0;
// 从I2S接口读取音频数据
//int ret=esp_codec_dev_read(record_dev, feed_buff, feed_chunksize * feed_nch * sizeof(int16_t)); // 读取音频数据(录音)
// 将左右声道数据求和取半,转为单声道数据
//for (int i = 0; i < feed_chunksize; i++) {
// int left = feed_buff[2*i];
// int right = feed_buff[2*i + 1];
// 求和并取平均,防止数值溢出
// mono_buff[i] = (left + right) >> 1;
//}
//ESP_LOGW(TAG_LVGL, "codec read error: %d", ret);
/*
if(counter < 33) // 采集100次后关闭文件(约10秒音频)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 33) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
counter++;
FILE* fp = fopen("/extflash/audio2.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"WAV 文件已保存:/extflash/audio1.wav\n");
ESP_LOGW(TAG_MAIN,"开始写入第二个文件:/extflash/audio2.wav\n");
}
if(counter > 33 && counter < 66)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 66) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第二个 WAV 文件已保存:/extflash/audio2.wav\n");
counter++;
FILE* fp = fopen("/extflash/audio3.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"开始写入第三个文件:/extflash/audio3.wav\n");
}
if(counter > 66 && counter < 99)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 99) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第三个 WAV 文件已保存:/extflash/audio3.wav\n");
counter++;
break; // 结束循环,停止采集
}
*/
// 将音频数据喂给音频前端处理
//ESP_LOGI("I2S RAW", "L:%d, R:%d, L:%d, R:%d,L:%d, R:%d,L:%d, R:%d", feed_buff[0], feed_buff[1], feed_buff[2], feed_buff[3], feed_buff[4], feed_buff[5], feed_buff[6], feed_buff[7]);
//int num_samples = feed_chunksize * feed_nch;
/*
long long sum_squares = 0;
for (int i = 0; i < num_samples; i++) {
sum_squares += (long long)feed_buff[i] * feed_buff[i];
}
double rms = sqrt((double)sum_squares / num_samples);
#define REFERENCE_RMS 750
double db = 20.0 * log10(rms / REFERENCE_RMS);
//ESP_LOGI("AUDIO", "RMS: %.2f, dB: %.2f", rms, db);
*/
//afe_handle->feed(afe_data_, feed_buff);
vTaskDelay(pdMS_TO_TICKS(1));
//ESP_LOGW(TAG_LVGL,"feed_task");
}
// 任务退出时释放资源
vTaskDelete(NULL);
}
extern bool is_ai_recording; // AI语音交互状态标志(外部声明)
extern bool is_ai_create_pcmfile; // AI语音交互生成pcm文件状态标志(外部声明)
extern bool is_ai_end_recording; // AI语音交互结束录音状态标志(外部声明)
void get_file_size(const char *path) {
struct stat st;
if (stat(path, &st) != 0) {
ESP_LOGW(TAG_MAIN,"stat failed for %s\n", path);
return;
}
float size_kb = st.st_size / 1024.0;
ESP_LOGI(TAG_MAIN, "File %s size: %.2f KB", path, size_kb);
}
/**
* @brief 语音检测任务
* 从音频前端获取处理后的数据并进行语音命令识别
*
* @param arg 音频前端数据句柄
*/
extern int64_t init_start_us;
int silence_count = 0;
void detect_Task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting detect task");
int feed_chunksize=512;
int feed_nch=2;
// 分配音频数据缓冲区
int16_t *feed_buff = (int16_t *)heap_caps_malloc(feed_chunksize * feed_nch * sizeof(int16_t), MALLOC_CAP_SPIRAM);
assert(feed_buff); // 确保内存分配成功
int16_t *mono_buff = (int16_t *)malloc(feed_chunksize * sizeof(int16_t));
if (mono_buff == NULL) {
ESP_LOGE(TAG_LVGL, "Failed to allocate mono buffer");
return;
}
record_dev = ES7243E_Init();
//esp_afe_sr_data_t *afe_data_ = arg;
// 获取获取数据的块大小并分配缓冲区
//int afe_chunksize = afe_handle->get_fetch_chunksize(afe_data_);
//ESP_LOGW(TAG_LVGL, "afe_chunksize: %d", afe_chunksize);
/*
FILE* fp = fopen("/extflash/audio1res.wav", "wb");
if (!fp) {
ESP_LOGW(TAG_MAIN, "WAV文件打开失败,无法写入");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 1;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 3.2; // 3.2秒总采样数(每声道)
uint32_t totalDataBytes = totalSamples * numChannels * (bitsPerSample / 8);
write_wav_header(fp, totalDataBytes);
int counter = 0;
*/
ESP_LOGW(TAG_LVGL, "开始识别");
const int64_t init_elapsed_us = esp_timer_get_time() - init_start_us;
ESP_LOGI(TAG_MAIN, "语音识别初始化耗时: %" PRId64 " ms (%" PRId64 " us)",
init_elapsed_us / 1000, init_elapsed_us);
while (1)
{
//对话开启,启动PCM文件创建
if(is_ai_create_pcmfile == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "wb");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件打开失败,无法写入,语音对话阶段一失败");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 2;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 2; // 2秒总采样数(每声道)
uint32_t totalDataBytes = 0; // PCM文件不需要头部,数据大小可以动态增长,需要最后填入
write_wav_header(fp, totalDataBytes);
is_ai_create_pcmfile = false;
ESP_LOGW(TAG_LVGL, "创建PCM基础格式成功");
is_ai_recording = true;//进入录音状态
fclose(fp);
}
int ret=esp_codec_dev_read(record_dev, feed_buff, feed_chunksize * feed_nch * sizeof(int16_t)); // 读取音频数据(录音)
//分贝检测
//音频录音
uint32_t totalDataBytes = 0;
if(is_ai_recording == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "ab");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件录音失败,无法写入,语音对话阶段二失败");
}
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
totalDataBytes += feed_chunksize * feed_nch * sizeof(int16_t);
//结束录音状态
fclose(fp);
//RMS对话结束检测函数
int num_samples = feed_chunksize * feed_nch;
long long sum_squares = 0;
for (int i = 0; i < num_samples; i++) {
sum_squares += (long long)feed_buff[i] * feed_buff[i];
}
double rms = sqrt((double)sum_squares / num_samples);
#define REFERENCE_RMS 750
double db = 20.0 * log10(rms / REFERENCE_RMS);
ESP_LOGI("AUDIO", "RMS: %.2f, dB: %.2f", rms, db);
if(rms<400){
silence_count++;
}
if(silence_count==53){
is_ai_recording = false;
is_ai_end_recording = true;
silence_count = 0;
}
}
// 录制结束,更新 PCM 文件头中的数据大小
if(is_ai_end_recording == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "ab");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件录音结束失败,语音对话阶段三失败");
}
fseek(fp, 0, SEEK_SET); // 定位到文件开头
write_wav_header(fp, totalDataBytes); // 更新 WAV 头部中的数据
fclose(fp);
get_file_size("/extflash/audio_ai.pcm");
ESP_LOGW(TAG_LVGL, "PCM文件录制完成并更新头部成功");
change_task_post_action(0X000C);
is_ai_end_recording = false; //重置结束录音状态
}
// 将左右声道数据求和取半,转为单声道数据
for (int i = 0; i < feed_chunksize; i++) {
int left = feed_buff[2*i];
int right = feed_buff[2*i + 1];
// 求和并取平均,防止数值溢出
int avg = (left + right) >> 1; // 取平均
mono_buff[i] = (int)avg;
}
ESP_LOGI("I2S RAW", "L:%d, R:%d, L:%d, R:%d,L:%d, R:%d,L:%d, R:%d", feed_buff[0], feed_buff[1], feed_buff[2], feed_buff[3], feed_buff[4], feed_buff[5], feed_buff[6], feed_buff[7]);
ESP_LOGI("I2S RAW", "1:%d, 2:%d, 3:%d, 4:%d,5:%d, 6:%d,7:%d, 8:%d", mono_buff[0], mono_buff[1], mono_buff[2], mono_buff[3], mono_buff[4], mono_buff[5], mono_buff[6], mono_buff[7]);
// 从音频前端获取处理后的数据
//ESP_LOGW(TAG_LVGL, "detect_task");
//afe_fetch_result_t *res = afe_handle->fetch(afe_data_);
//int sample_count = res->data_size / sizeof(int16_t);
// 检查获取结果是否有效
//if (!res || res->ret_value == ESP_FAIL)
//{
// ESP_LOGE(TAG_LVGL, "获取音频数据失败!");
// res = afe_handle->fetch(afe_data_);
// break;
//}
/*
if(counter < 100) // 采集100次后关闭文件(约10秒音频)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 100) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
counter++;
FILE* fp = fopen("/extflash/audio2res.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"WAV 文件已保存:/extflash/audio1res.wav\n");
ESP_LOGW(TAG_MAIN,"开始写入第二个文件:/extflash/audio2res.wav\n");
}
if(counter > 100 && counter < 200)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 200) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第二个 WAV 文件已保存:/extflash/audio2res.wav\n");
counter++;
FILE* fp = fopen("/extflash/audio3res.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"开始写入第三个文件:/extflash/audio3res.wav\n");
}
if(counter > 200 && counter < 300)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 300) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第三个 WAV 文件已保存:/extflash/audio3res.wav\n");
counter++;
break; // 结束循环,停止采集
}
// 获取并处理 VAD 状态
if (res->vad_state == 1) {
// 检测到语音活动
ESP_LOGI(TAG_LVGL, "VAD: Speech detected.");
} else if (res->vad_state == 0) {
// 当前为静音或噪声
ESP_LOGE(TAG_LVGL, "VAD: Silence.");
}
*/
/*
int sample_count = res->data_size / sizeof(int16_t);
int16_t *samples = res->data;
ESP_LOGI(TAG_LVGL, "data[0~5] = %d, %d, %d, %d, %d, %d",
samples[0], samples[1], samples[2],
samples[3], samples[4], samples[5]);
int16_t *samples2 = res->raw_data;
ESP_LOGI(TAG_LVGL, "raw_data[0~5] = %d, %d, %d, %d, %d, %d",
samples2[0], samples2[1], samples2[2],
samples2[3], samples2[4], samples2[5]);
*/
//ESP_LOGI(TAG_LVGL, "data_volume = %.2f dB, ringbuff_free_pct = %.2f%%",
// res->data_volume, res->ringbuff_free_pct * 100);*/
// 直接进行语音命令识别,跳过唤醒词检测
// 使用多网络模型进行语音命令检测
esp_mn_state_t mn_state = ESP_MN_STATE_DETECTING;
mn_state = multinet->detect(model_data, mono_buff);
// 如果仍在检测中,继续循环
if (ESP_MN_STATE_DETECTING == mn_state)
{
//ESP_LOGI(TAG_LVGL, "语音识别中...");
continue;
}
// 处理超时情况
if (ESP_MN_STATE_TIMEOUT == mn_state)
{
ESP_LOGW(TAG_LVGL, "语音识别超时");
sr_result_t result =
{
.state = mn_state,
.command_id = 0,
};
// xQueueSend(g_result_que, &result, 10);
continue;
}
// 处理识别到命令的情况
if (ESP_MN_STATE_DETECTED == mn_state)
{
// 获取识别结果
esp_mn_results_t *mn_result = multinet->get_results(model_data);
for (int i = 0; i < mn_result->num; i++)
{
int prob_permille = (int)(mn_result->prob[i] * 1000.0f);
ESP_LOGI(TAG_LVGL, "TOP %d, command_id: %d, phrase_id: %d, prob: %d.%03d",
i + 1, mn_result->command_id[i], mn_result->phrase_id[i],
prob_permille / 1000, abs(prob_permille % 1000));
}
int sr_command_id = mn_result->command_id[0];
// 在这里添加指令对应的中文打印
switch (sr_command_id)
{
case 0: // "ni hao xiao qi"
ESP_LOGE(TAG_LVGL, "识别到指令:你好小奇");
change_task_post_action(0X000B);
is_ai_create_pcmfile = true;
//ret =lv_obj_send_event(ui_Screen_Lottie, LV_EVENT_KEY, (void *)LV_KEY_ENTER);
ESP_LOGW(TAG_LVGL, "RET: %d", ret);
break;
case 1: // "huan ge feng ge"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,再见");
change_task_post_action(0X000C);
//ret =lv_obj_send_event(ui_Screen_Lottie, LV_EVENT_KEY, (void *)LV_KEY_NEXT);
ESP_LOGW(TAG_LVGL, "RET: %d", ret);
break;
case 2: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,弹个吉他");
break;
case 3: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,比个心");
break;
case 4: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,思考一下这个问题");
break;
default:
ESP_LOGE(TAG_LVGL, "识别到未知指令:%d", sr_command_id);
break;
}
// 组织识别结果
sr_result_t result = {
.state = mn_state,
.command_id = sr_command_id,
};
}
vTaskDelay(pdMS_TO_TICKS(1));
}
if (feed_buff)
{
free(feed_buff);
feed_buff = NULL;
}
if (mono_buff)
{
free(mono_buff);
mono_buff = NULL;
}
vTaskDelete(NULL);
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef _SPEECH_APP_H_
#define _SPEECH_APP_H_
#ifdef __cplusplus
extern "C" {
#endif
// #include "_stdint.h"
#include "config.h"
extern const uint32_t cmd_phoneme_number;
/* 拼音命令词表(与 multinet 命令短语一一对应) */
extern const char *cmd_phoneme[];
// 音频设备句柄
extern esp_codec_dev_handle_t record_dev;
void feed_Task(void *arg);
void detect_Task(void *arg);
extern lv_obj_t *ui_Screen_Lottie; // 专门用于播放动画的独立屏幕
#ifdef __cplusplus
}
#endif
#endif /* _SPEECH_APP_H_ */
+99
View File
@@ -0,0 +1,99 @@
#include "speech_init.h"
#include "speech_app.h"
#include "main.h"
srmodel_list_t *models = NULL;
afe_config_t *afe_config = NULL;
esp_afe_sr_iface_t *afe_handle = NULL;
esp_afe_sr_data_t *afe_data = NULL;
const esp_mn_iface_t *multinet = NULL;
model_iface_data_t *model_data = NULL;
char *mn_name = NULL;
static const char *TAG_LVGL = "sr";
esp_err_t app_sr_init(void)
{
//record_dev = ES7243E_Init();
/* 配置音频前端参数 */
// srmodel_list_t *models = esp_srmodel_init("model");
/*
models = esp_srmodel_init("model");
// afe_config_t *afe_config = afe_config_init("MM", models, AFE_TYPE_SR, AFE_MODE_LOW_COST);
afe_config = afe_config_init("MM", models, AFE_TYPE_SR, AFE_MODE_LOW_COST);
// afe_config>memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_config->wakenet_model_name = NULL;
afe_config->aec_init = false; // 关闭回声消除(根据需求调整)
afe_config->vad_init = false; // 启用VAD(语音活动检测)
afe_config->pcm_config.total_ch_num = 2; // 总通道数 = 2
afe_config->pcm_config.mic_num = 2; // 麦克风通道数 = 2
afe_config->pcm_config.ref_num = 0; // 回放参考通道数 = 0
afe_config->afe_ringbuf_size = 20;
//afe_config->vad_mode = VAD_MODE_0;
afe_config->agc_init = false; // 关闭自动增益控制(根据需求调整)
afe_config->ns_init = false;
afe_config->afe_linear_gain = 2.0;
ESP_LOGE(TAG_LVGL, "配置前 afe_ringbuf_size = %d", afe_config->afe_ringbuf_size);
afe_handle = esp_afe_handle_from_config(afe_config);
// esp_afe_sr_data_t *afe_data = afe_handle->create_from_config(afe_config);
afe_data = afe_handle->create_from_config(afe_config);
*/
// char *mn_name = esp_srmodel_filter(models, ESP_MN_CHINESE, NULL);
srmodel_list_t *models = esp_srmodel_init("model");
if (models == NULL) {
ESP_LOGE(TAG_LVGL, "未找到 model 分区,跳过语音模型");
return ESP_OK;
}
mn_name = esp_srmodel_filter(models, ESP_MN_CHINESE, NULL);
if (NULL == mn_name)
{
ESP_LOGE(TAG_LVGL, "未找到多网络模型");
return ESP_OK;
}
multinet = esp_mn_handle_from_name(mn_name);
model_data = multinet->create(mn_name, 30000); // 设置语音识别超时时间
/* 配置语音命令 */
esp_mn_commands_clear(); // 清除现有命令
// 添加所有语音命令
for (int i = 0; i < cmd_phoneme_number; i++)
{
esp_mn_commands_add(i, (char *)cmd_phoneme[i]);
}
esp_mn_commands_update(); // 更新命令词列表
esp_mn_commands_print();
multinet->print_active_speech_commands(model_data); // 输出目前激活的命令词
afe_config_free(afe_config); /* 释放配置内存 */
feed_task_stack = heap_caps_malloc(FEED_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
detect_task_stack = heap_caps_malloc(DETECT_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (feed_task_stack == NULL || detect_task_stack == NULL)
{
ESP_LOGE(TAG_LVGL, "PSRAM 内存分配失败");
return ESP_OK;
}
//xTaskCreateStaticPinnedToCore(feed_Task, "feed", FEED_TASK_STACK_SIZE, (void *)afe_data, FEED_TASK_PRIORITY, feed_task_stack, &feed_task_tcb, 1);
xTaskCreateStaticPinnedToCore(detect_Task, "detect", DETECT_TASK_STACK_SIZE, (void *)afe_data, DETECT_TASK_PRIORITY, detect_task_stack, &detect_task_tcb, 1);
return ESP_OK;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef _SPEECH_INIT_H_
#define _SPEECH_INIT_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
#include "config.h"
extern srmodel_list_t *models;
extern afe_config_t *afe_config;
extern esp_afe_sr_iface_t *afe_handle;
extern esp_afe_sr_data_t *afe_data;
extern const esp_mn_iface_t *multinet;
extern model_iface_data_t *model_data;
extern char *mn_name;
// /** 语音模块初始化(模型/命令词/任务等) */
// esp_err_t speech_init(void);
// /** 语音模块反初始化(释放模型/队列/任务等) */
// esp_err_t speech_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* _SPEECH_INIT_H_ */
+15
View File
@@ -0,0 +1,15 @@
#include "system.h"
SYSTEM_INFO system_info =
{
.s_emoji_pack =
{
.using_pack = 0,
}
};
void get_system_info(void)
{
system_info.s_emoji_pack.using_pack = '0';//表示正在使用默认的表情包
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
typedef struct
{
char using_pack;//正在使用的表情包系列
}EMOJI_PACK;
typedef struct
{
EMOJI_PACK s_emoji_pack;
}SYSTEM_INFO;
extern SYSTEM_INFO system_info;
#endif
+332
View File
@@ -0,0 +1,332 @@
#include "UI_DISPATCH/almanac_service.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cjson.h"
#include "esp_log.h"
static almanac_provider_t s_provider = {0};
static const char *TAG = "almanac_service";
/* main/CMakeLists.txt: EMBED_TXTFILES "UI_DISPATCH/yiji_data_2026_2030.json" */
extern const char yiji_json_start[] asm("_binary_yiji_data_2026_2030_json_start");
extern const char yiji_json_end[] asm("_binary_yiji_data_2026_2030_json_end");
static cJSON *s_yiji_root = NULL;
static bool s_yiji_load_attempted = false;
/* 1900~2099 农历数据,bit 编码规则沿用常见农历算法实现 */
static const uint32_t LUNAR_INFO[] = {
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06aa0, 0x1a6c4, 0x0aae0,
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
};
typedef struct {
int year;
int month;
int day;
bool is_leap_month;
} lunar_date_t;
static int64_t days_from_civil(int y, unsigned m, unsigned d)
{
y -= (m <= 2);
{
const int era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = (unsigned)(y - era * 400);
const unsigned doy = (153 * (m + (m > 2 ? (unsigned)-3 : 9)) + 2) / 5 + d - 1;
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + (int64_t)doe - 719468;
}
}
static int lunar_leap_month(int year)
{
return (int)(LUNAR_INFO[year - 1900] & 0x0f);
}
static int lunar_leap_days(int year)
{
const int lm = lunar_leap_month(year);
if (lm == 0) {
return 0;
}
return (LUNAR_INFO[year - 1900] & 0x10000) ? 30 : 29;
}
static int lunar_month_days(int year, int month)
{
return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
static int lunar_year_days(int year)
{
int sum = 348;
uint32_t mask = 0;
for (mask = 0x8000; mask > 0x8; mask >>= 1) {
if (LUNAR_INFO[year - 1900] & mask) {
sum += 1;
}
}
return sum + lunar_leap_days(year);
}
static bool solar_to_lunar(int year, int month, int day, lunar_date_t *out)
{
int64_t offset = 0;
int lunar_year = 0;
int year_days = 0;
int leap = 0;
bool in_leap = false;
int lunar_month = 0;
int month_days = 0;
if (out == NULL) {
return false;
}
if (year < 1900 || year > 2099 || month < 1 || month > 12 || day < 1 || day > 31) {
return false;
}
offset = days_from_civil(year, (unsigned)month, (unsigned)day) - days_from_civil(1900, 1, 31);
if (offset < 0) {
return false;
}
lunar_year = 1900;
year_days = lunar_year_days(lunar_year);
while (lunar_year < 2100 && offset >= year_days) {
offset -= year_days;
lunar_year++;
if (lunar_year <= 2099) {
year_days = lunar_year_days(lunar_year);
}
}
if (lunar_year > 2099) {
return false;
}
leap = lunar_leap_month(lunar_year);
lunar_month = 1;
while (lunar_month <= 12) {
if (leap > 0 && lunar_month == (leap + 1) && !in_leap) {
lunar_month -= 1;
in_leap = true;
month_days = lunar_leap_days(lunar_year);
} else {
month_days = lunar_month_days(lunar_year, lunar_month);
}
if (offset < month_days) {
break;
}
offset -= month_days;
if (in_leap && lunar_month == leap) {
in_leap = false;
}
lunar_month++;
}
out->year = lunar_year;
out->month = lunar_month;
out->day = (int)offset + 1;
out->is_leap_month = in_leap;
return true;
}
static const char *lunar_month_to_text(int month)
{
static const char *month_text[] = {
"", "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊",
};
if (month < 1 || month > 12) {
return "";
}
return month_text[month];
}
static const char *lunar_day_to_text(int day)
{
static const char *day_text[] = {
"", "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十",
};
if (day < 1 || day > 30) {
return "";
}
return day_text[day];
}
static bool almanac_builtin_get(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out)
{
lunar_date_t lunar = {0};
cJSON *item = NULL;
cJSON *node_year = NULL;
cJSON *node_month = NULL;
cJSON *node_day = NULL;
cJSON *yi = NULL;
cJSON *ji = NULL;
int n = 0;
int i = 0;
size_t used = 0;
const char *word = NULL;
if (out == NULL) {
return false;
}
memset(out, 0, sizeof(*out));
if (solar_to_lunar((int)year, (int)month, (int)day, &lunar)) {
snprintf(out->lunar, sizeof(out->lunar), "%s%s月%s",
lunar.is_leap_month ? "闰" : "",
lunar_month_to_text(lunar.month),
lunar_day_to_text(lunar.day));
}
/* 内置 JSON 懒加载:只解析一次,失败后不重复尝试 */
if (!s_yiji_load_attempted) {
const char *json_start = yiji_json_start;
const char *json_end = yiji_json_end;
s_yiji_load_attempted = true;
if (json_end > json_start) {
const size_t json_len = (size_t)(json_end - json_start);
char *json_buf = (char *)malloc(json_len + 1);
if (json_buf != NULL) {
memcpy(json_buf, json_start, json_len);
json_buf[json_len] = '\0';
s_yiji_root = cJSON_Parse(json_buf);
free(json_buf);
if (s_yiji_root == NULL) {
ESP_LOGW(TAG, "parse yiji_data json failed");
}
} else {
ESP_LOGW(TAG, "malloc yiji_data buffer failed");
}
}
}
if (s_yiji_root != NULL && cJSON_IsArray(s_yiji_root)) {
n = cJSON_GetArraySize(s_yiji_root);
for (i = 0; i < n; i++) {
item = cJSON_GetArrayItem(s_yiji_root, i);
if (!cJSON_IsObject(item)) {
continue;
}
node_year = cJSON_GetObjectItem(item, "lunar_year");
node_month = cJSON_GetObjectItem(item, "lunar_month");
node_day = cJSON_GetObjectItem(item, "lunar_day");
if (!cJSON_IsNumber(node_year) || !cJSON_IsNumber(node_month) || !cJSON_IsNumber(node_day)) {
continue;
}
/*
* 约定:JSON 中 lunar_year/lunar_month/lunar_day 作为“查表日期键”使用,
* 这里按传入公历日期(year/month/day)直接匹配。
* 这样修改 JSON 的 2026-09-17 会直接影响界面显示。
*/
if (node_year->valueint != (int)year ||
node_month->valueint != (int)month ||
node_day->valueint != (int)day) {
continue;
}
yi = cJSON_GetObjectItem(item, "yi");
ji = cJSON_GetObjectItem(item, "ji");
used = 0;
if (cJSON_IsArray(yi)) {
const int yi_n = cJSON_GetArraySize(yi) < 4 ? cJSON_GetArraySize(yi) : 4;
for (int k = 0; k < yi_n; k++) {
cJSON *w = cJSON_GetArrayItem(yi, k);
if (!cJSON_IsString(w) || w->valuestring == NULL) {
continue;
}
word = w->valuestring;
if (used > 0 && used < sizeof(out->dos) - 1) {
out->dos[used++] = ' ';
out->dos[used] = '\0';
}
if (used < sizeof(out->dos) - 1) {
used += (size_t)snprintf(out->dos + used, sizeof(out->dos) - used, "%s", word);
if (used >= sizeof(out->dos)) {
used = sizeof(out->dos) - 1;
}
}
}
}
used = 0;
if (cJSON_IsArray(ji)) {
const int ji_n = cJSON_GetArraySize(ji) < 4 ? cJSON_GetArraySize(ji) : 4;
for (int k = 0; k < ji_n; k++) {
cJSON *w = cJSON_GetArrayItem(ji, k);
if (!cJSON_IsString(w) || w->valuestring == NULL) {
continue;
}
word = w->valuestring;
if (used > 0 && used < sizeof(out->donts) - 1) {
out->donts[used++] = ' ';
out->donts[used] = '\0';
}
if (used < sizeof(out->donts) - 1) {
used += (size_t)snprintf(out->donts + used, sizeof(out->donts) - used, "%s", word);
if (used >= sizeof(out->donts)) {
used = sizeof(out->donts) - 1;
}
}
}
}
break;
}
}
if (out->dos[0] == '\0') {
snprintf(out->dos, sizeof(out->dos), "-- -- -- --");
}
if (out->donts[0] == '\0') {
snprintf(out->donts, sizeof(out->donts), "-- -- -- --");
}
return true;
}
void almanac_service_set_provider(const almanac_provider_t *provider)
{
if (provider == NULL || provider->get == NULL) {
s_provider.get = NULL;
s_provider.ctx = NULL;
return;
}
s_provider = *provider;
}
bool almanac_service_get(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out)
{
if (out == NULL) {
return false;
}
if (s_provider.get != NULL && s_provider.get(year, month, day, out, s_provider.ctx)) {
return true;
}
return almanac_builtin_get(year, month, day, out);
}

Some files were not shown because too many files have changed in this diff Show More