ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32-S2双模收音机:ESP-IDF+ESP-ADF+LVGL实战解析

ESP32-S2双模收音机:ESP-IDF+ESP-ADF+LVGL实战解析 简介一套围绕ESP32S2的嵌入式收音机项目资料包基于ESP-IDF、ESP-ADF与LVGL框架实现网络收音机与FM收音机功能面向毕业设计、课程设计、工程实训等嵌入式开发场景。压缩包共1519个文件、48.96MB主要内容包括C与H源码、Python辅助脚本、Markdown与RST说明文档、PNG/JPG图片素材并附带PDF设计报告、LVGL中文字体与动画资源以及大量ESP-IDF配置文件整体目录结构清晰便于按模块查阅和修改。项目从网络连接、音频解码到屏幕交互均有对应实现可帮助学习者理解网络收音机从底层驱动到上层界面的完整开发链路。目前已有150人学习下载该项目在答辩评审中平均得分96分整体完整度和可复现性较好。使用者可直接获得完整工程源码、设计文档、界面素材与调试辅助脚本既能照着现有代码复刻同款设备也可在此基础上扩展蓝牙播放、语音交互、更多音频源等新功能适合嵌入式学习与课程项目研发参考。1. ESP32-S2 做收音机的整体链路为什么是 ESP-IDF、ESP-ADF、LVGL 三件套答辩前一周才发现手上这块 ESP32-S2 既没有蓝牙官方例程里那份基于 ES8388 的板级配置也直接套不上——这是不少做网络收音机加 FM 收音机课设的人会撞的第一堵墙。ESP32-S2 是单核 Xtensa LX7、240 MHz、320 KB SRAM外挂 PSRAM 的模组通常有 2 MB 或 4 MB片上只有一路 I2S 和两路 I2CWiFi 是 2.4 GHz 单频。这个规格决定了三件套的分工ESP-IDF 管芯片初始化、WiFi、NVS 和任务调度ESP-ADF 把「HTTP 拉流 → MP3 解码 → I2S 输出」封装成可拼接的 pipelineLVGL 负责屏幕上的电台列表、音量弧和 FM 频点显示。FM 这一路一般不占 I2S常见做法是外挂 RDA5807M 或 TEA5767 这类芯片用 I2C 写寄存器调谐音频从芯片模拟脚直接进功放。所以整个系统的本质是「两条独立音源 一套 UI 一个调度器」。适合正在做毕设、课设、实训大作业或者想一次性把嵌入式音频链路打通的人。2. ESP-IDF 环境与工程骨架从创建工程到两路 I2C 总线分配2.1 ESP-IDF 安装方式与 idf.py 创建工程装 IDF 走两条路。离线安装包适合不想折腾 Python 依赖的课设场景装完打开对应终端就能用命令行方式适合需要切版本、跟上游 master 的场合。VS Code 插件那条路坑最多the path for esp-idf is not valid: /tools/idf.py not found这个报错基本都是插件里配的idfPath指到了esp-idf的上级目录而不是仓库根目录或者指向了不存在的版本目录。离线安装时如果选了安装路径组件仍会被放到默认的.espressif目录下这属于正常行为不是装错了。建工程的命令很短但顺序不能反# 1. 载入 IDF 环境Linux/macOS . $HOME/esp/esp-idf/export.sh # Windows 下用 export.bat 或安装包生成的快捷终端 # 2. 基于模板建工程比从空目录手搓 CMakeLists.txt 快得多 idf.py create-project net_radio_fm cd net_radio_fm # 3. 先定 target 再动代码后面所有 sdkconfig 默认值都跟着它走 idf.py set-target esp32s2 idf.py menuconfigcreate-project生成的是最小骨架main/CMakeLists.txt里用idf_component_register(SRCS ... REQUIRES ...)声明依赖。ADF 和 LVGL 都以组件形式挂在components/或通过idf_component.yml拉取前者适合课设要求源码可见的场景后者适合快速验证。改完 CMake 记得idf.py reconfigure否则新加的组件不会进构建系统。2.2 menuconfig 里必须先确认的几组参数S2 出问题的案例里一半以上是内存和缓冲没配。下面这份是跑通网络收音机的最低要求缺一项就可能在解码阶段重启配置项建议值说明CONFIG_ESP32S2_SPIRAMy模组带 PSRAM 时解码缓冲和 LVGL 缓冲都靠它CONFIG_SPIRAM_USE_MALLOCy让malloc自动落到 PSRAMCONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL8192 左右小内存块留在片内避免 PSRAM 访问慢拖累中断CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ240降频会直接导致 MP3 解码丢帧CONFIG_FREERTOS_UNICOREyS2 本来就是单核开了能省一层调度开销CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM16 以上默认值在流媒体下容易丢包CONFIG_LWIP_TCP_WND_DEFAULT32 KB 以上窗口太小会限制吞吐CONFIG_PARTITION_TABLE_SINGLE_APP_LARGEy带 ADF 的固件很容易超过 1 MB改完用idf.py build编一次idf.py size-components看各组件占用。ADF 和 LVGL 同时进去flash 占用通常在两三 MB分区表一定要提前放大。2.3 ESP-IDF 设置两个 I2C 接口触摸和 FM 芯片分总线这块板子上有两个 I2C 从设备一个是屏幕的电容触摸GT911、CST816 这类一个是 FM 芯片。把两者挂同一条总线并非不行但触摸的中断式读取和 FM 的寄存器写入会在同一把总线上抢时序调试阶段很难判断是谁把谁拖死的。常见做法是分开总线用途典型引脚速率I2C0触摸屏GPIO 8/9400 kHzI2C1FM 芯片GPIO 10/11100 kHzSPI屏幕显示GPIO 12/13/14/1540 MHzESP-IDF 5.x 起推荐用driver/i2c_master.h这套新驱动两路总线的初始化和旧驱动写法差别很大#include driver/i2c_master.h static i2c_master_bus_handle_t bus_touch, bus_fm; static i2c_master_dev_handle_t dev_touch, dev_fm; void i2c_buses_init(void) { /* 触摸总线400kHz屏幕刷新频繁需要快一点 */ i2c_master_bus_config_t cfg_touch { .i2c_port I2C_NUM_0, .sda_io_num GPIO_NUM_8, .scl_io_num GPIO_NUM_9, .clk_source I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt 7, .flags.enable_internal_pullup true, }; ESP_ERROR_CHECK(i2c_new_master_bus(cfg_touch, bus_touch)); /* FM 总线100kHz寄存器写不追求速度稳定优先 */ i2c_master_bus_config_t cfg_fm { .i2c_port I2C_NUM_1, .sda_io_num GPIO_NUM_10, .scl_io_num GPIO_NUM_11, .clk_source I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt 7, .flags.enable_internal_pullup true, }; ESP_ERROR_CHECK(i2c_new_master_bus(cfg_fm, bus_fm)); /* 挂载从设备地址先扫出来再填 */ i2c_device_config_t d_touch { .dev_addr_length I2C_ADDR_BIT_LEN_7, .device_address 0x5D, /* GT911 常见地址之一另一路是 0x14 */ .scl_speed_hz 400000, }; ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_touch, d_touch, dev_touch)); i2c_device_config_t d_fm { .dev_addr_length I2C_ADDR_BIT_LEN_7, .device_address 0x10, /* RDA5807M 的 7 位写地址 */ .scl_speed_hz 100000, }; ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_fm, d_fm, dev_fm)); }地址别照抄。上电后先用i2c_master_probe(bus_fm, 0x10, 100)把0x10到0x17扫一遍把实际响应的地址记下来。RDA5807M 的读地址和写地址是两个连续值写用低的一个读用高的一个扫描时两个都会出现别以为扫出两个就是两个器件。2.4 i2c_master_write_byte 在新旧驱动里的处理差异很多从老教程抄代码的人会卡在i2c_master_write_byte找不到符号。这个函数属于旧的driver/i2c.h驱动签名是i2c_master_write_byte(i2c_cmd_handle_t cmd, uint8_t data, bool ack_en)必须配合i2c_cmd_link_create()、i2c_master_cmd_begin()组成命令链使用。新的driver/i2c_master.h没有这个函数取而代之的是i2c_master_transmit()和i2c_master_transmit_receive()一次调用完成「起始 地址 数据 停止」的完整事务。两种写法不能在同一个工程里混用混了要么链接报未定义符号要么运行期总线状态错乱。如果手里有大量旧代码要迁写一个薄封装最省事/* 用新驱动模拟旧驱动的 write_byte 语义发一个字节并检查 ACK */ static esp_err_t i2c_write_reg16(i2c_master_dev_handle_t dev, uint8_t reg, uint16_t value) { uint8_t buf[3] { reg, (uint8_t)(value 8), (uint8_t)(value 0xFF) }; /* timeout 给足FM 芯片在调谐瞬间会拉长时钟 */ return i2c_master_transmit(dev, buf, sizeof(buf), 100); } static esp_err_t i2c_read_regs(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t *out, size_t len) { /* 先写寄存器地址再读回 len 字节一个事务完成 */ return i2c_master_transmit_receive(dev, reg, 1, out, len, 100); }i2c_master_transmit的第四个参数是超时毫秒数不是重试次数写 0 表示一直等实际项目里别这么干总线被拉死时任务会永久挂起。i2c_master_transmit_receive的语义是「先发再收」正好对应绝大多数 I2C 从设备的寄存器读时序不用手动拼两个事务。3. ESP-ADF 音频通路网络收音机的 pipeline 与 FM 音源切换3.1 ESP-ADF 里没有现成的 ESP32-S2 板级配置怎么办ADF 的board/目录预置的板级配置以官方开发板为主S2 的板子基本要自己加一份。板级配置的本质是提供一组函数指针告诉 ADF「这块板上有没有 codec、I2S 用哪几个脚、音量怎么设」。如果板子上用的是 ES8388、AC101 这类需要寄存器配置的 codec就得照着同类板子复制一份把 I2C 地址和寄存器初始化序列改掉。更省事的一种情况板子用的是 PCM5102A、MAX98357 这类不需要配置的 DAC 或 I2S 功放。这时候 codec 那层可以直接跳过i2s_stream配成写模式接到管道末端就能出声不需要板级文件的 codec 部分。课设里推荐这条路能砍掉一半的调试时间。复制板级目录时注意Kconfig.projbuild里要加一个选项否则 menuconfig 的 Audio HAL 列表里根本看不到自己的板子audio_board_init()会返回 NULL后面的 pipeline 全部起不来。3.2 http_stream mp3_decoder i2s_stream 的最小 pipeline网络收音机这条链路的三个 element 是固定搭配http_stream负责拉流mp3_decoder做软解i2s_stream把 PCM 推给 I2S。下面的骨架可以直接抄#include audio_pipeline.h #include http_stream.h #include mp3_decoder.h #include i2s_stream.h static audio_pipeline_handle_t s_pipeline; static audio_element_handle_t s_http, s_mp3, s_i2s; static volatile bool s_running; static esp_err_t stream_event_cb(audio_event_iface_msg_t *msg, void *ctx) { /* 解码器报告结束时把 pipeline 停掉交给上层决定是否重连 */ if (msg-source_type AUDIO_ELEMENT_TYPE_ELEMENT msg-source (void *)s_mp3 msg-cmd AEL_MSG_CMD_REPORT_STATUS) { audio_element_state_t st audio_element_get_state(s_mp3); if (st AEL_STATE_FINISHED) { s_running false; } } return ESP_OK; } esp_err_t net_radio_start(const char *url) { audio_pipeline_cfg_t pipe_cfg DEFAULT_AUDIO_PIPELINE_CONFIG(); s_pipeline audio_pipeline_init(pipe_cfg); http_stream_cfg_t http_cfg HTTP_STREAM_CFG_DEFAULT(); http_cfg.type AUDIO_STREAM_READER; http_cfg.url url; /* 指向你自己的流媒体地址 */ http_cfg.buffer_len 4096; /* 有 PSRAM 时可以放大到 8192 */ http_cfg.task_stack 4096; http_cfg.task_prio 5; http_cfg.out_rb_size 8 * 1024; /* 环形缓冲抗抖动靠它 */ http_cfg.event_handle stream_event_cb; s_http http_stream_init(http_cfg); mp3_decoder_cfg_t mp3_cfg DEFAULT_MP3_DECODER_CONFIG(); mp3_cfg.task_stack 8192; /* 软解吃栈别抠这 4KB */ mp3_cfg.task_prio 6; mp3_cfg.out_rb_size 8 * 1024; s_mp3 mp3_decoder_init(mp3_cfg); i2s_stream_cfg_t i2s_cfg I2S_STREAM_CFG_DEFAULT(); i2s_cfg.type AUDIO_STREAM_WRITER; i2s_cfg.i2s_config.sample_rate 44100; i2s_cfg.i2s_config.bits_per_sample I2S_BITS_PER_SAMPLE_16BIT; i2s_cfg.i2s_config.channel_format I2S_CHANNEL_FMT_RIGHT_LEFT; s_i2s i2s_stream_init(i2s_cfg); audio_pipeline_register(s_pipeline, s_http, http); audio_pipeline_register(s_pipeline, s_mp3, mp3); audio_pipeline_register(s_pipeline, s_i2s, i2s); const char *link_tag[3] { http, mp3, i2s }; audio_pipeline_link(s_pipeline, link_tag, 3); s_running true; return audio_pipeline_run(s_pipeline); }几个参数值得单独说。out_rb_size是 element 之间的环形缓冲网络抖动全靠它吸收8 KB 是下限默认的 4 KB 在 WiFi 信号一般时就会频繁断音。task_prio上解码器要高于拉流任务否则拉流任务把 CPU 占满时解码来不及消费。buffer_len是单次 HTTP 读的字节数设得比 TCP 窗口还大会适得其反。换台不用重建管道用audio_element_set_uri(s_http, new_url)加上重连逻辑即可但要记得在切换前调用audio_pipeline_pause()切完再audio_pipeline_resume()否则会听到一段杂音。3.3 FM 芯片的寄存器初始化与搜台RDA5807M 这类芯片的控制面很薄就是往连续寄存器写 16 位值。频率和寄存器值的换算关系是固定的调谐通道值 频率(MHz) × 10 − 870比如 100.0 MHz 对应 130。寄存器关键位作用02HENABLE芯片使能写 0 进软关断02HSOFT_RESET写 1 复位复位后需重新配置02HTUNE置 1 触发调谐完成后自动清零02HBAND频段选择87~108 MHz 对应一个固定值03HCHAN10 位通道号占 03H 的高位部分05HSEEK / SEEKUP自动搜台触发与方向05HVOLUME音量4 位0~15不同资料对位序的排法不一致写完先读回校验一次比对着手册看半天快。写寄存器的封装如下/* 从 02H 开始连续写 6 个寄存器一次事务发完避免中间被打断 */ static esp_err_t rda_write_all(i2c_master_dev_handle_t dev, uint16_t *reg) { uint8_t buf[12]; for (int i 0; i 6; i) { buf[i * 2] (uint8_t)(reg[i] 8); /* 高字节在前 */ buf[i * 2 1] (uint8_t)(reg[i] 0xFF); } return i2c_master_transmit(dev, buf, sizeof(buf), 200); } esp_err_t rda_tune(i2c_master_dev_handle_t dev, float freq_mhz) { uint16_t reg[6] {0}; uint16_t chan (uint16_t)(freq_mhz * 10 - 870); reg[0] (1u 0) /* ENABLE */ | (1u 4) /* TUNE本次写触发调谐 */ | (0u 2); /* BAND87~108MHz */ reg[1] (uint16_t)(chan 6); /* CHAN 占 03H 高位 */ reg[2] 0; reg[3] 0x8000; /* 音量相关位按模块实测调整 */ reg[4] 0; reg[5] 0; ESP_RETURN_ON_ERROR(rda_write_all(dev, reg), rda, tune write fail); vTaskDelay(pdMS_TO_TICKS(60)); /* 等调谐完成期间不要动总线 */ /* 调谐成功后芯片会清掉 TUNE 位读回确认再往下走 */ reg[0] ~(1u 4); return rda_write_all(dev, reg); }调谐期间总线不要去碰别的设备S2 单核下 I2C 任务和被抢占的任务容易在时钟拉伸阶段撞车。搜台把 SEEK 位置 1 后轮询状态寄存器的 STC 位超时给 2 秒以上弱信号区扫台本来就要时间。3.4 网络音源和 FM 音源的切换与资源释放两路音源在物理上就是分开的网络流走 I2S 到 DACFM 走芯片模拟输出到功放。所以「切换」这个动作拆开是三件事——停掉当前正在跑的 pipeline、把功放静音或切到另一路输入、启动新音源。对比项网络收音机FM 收音机数据通路WiFi → HTTP → MP3 软解 → I2S芯片内部解调 → 模拟输出ESP32-S2 占用CPU 高、内存高、WiFi 常开只占一路 I2C控制接口HTTP 换 URII2C 写寄存器切换动作pipeline pause/resume写 TUNE 位切到 FM 时不要只把 pipeline 暂停WiFi 连接和 HTTP 的 socket 留着会持续占用内存和射频资源。稳妥顺序是audio_pipeline_stop()→audio_pipeline_wait_for_stop()→audio_pipeline_deinit()最后audio_pipeline_unregister逐个注销 element。只 stop 不 deinit 是内存泄漏的重灾区来回切十几次就会因为申请不到环形缓冲而失败。切回来时重新 init 一遍代价是几百毫秒换来的是可控的内存曲线。4. LVGL 在 ESP32-S2 上的移植与界面开发4.1 FreeRTOS 环境下移植 LVGL用 esp_lvgl_port 而不是手搓 tick手搓移植的经典写法是在app_main里while(1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); }同时挂一个 1 ms 的esp_timer调lv_tick_inc。这套在只有 UI 的工程里没问题一旦和音频任务共存就会出问题lv_timer_handler是阻塞式的渲染一帧期间音频任务抢不到 CPU解码就断。现成的组件封装好了这件事工程里通过idf_component.yml声明依赖即可dependencies: idf: 5.0 lvgl/lvgl: ^8 espressif/esp_lvgl_port: ^2初始化时把显示和触摸都注册进去组件会自己起任务、自己管锁const lvgl_port_cfg_t port_cfg ESP_LVGL_PORT_INIT_CONFIG(); port_cfg.task_priority 2; /* 低于音频任务 */ port_cfg.task_stack 6144; port_cfg.timer_period_ms 5; lvgl_port_init(port_cfg); lvgl_port_display_cfg_t disp_cfg { .buffer_size 240 * 40, /* 40 行缓冲部分刷新 */ .double_buffer true, .hres 320, .vres 240, .flags { .buff_dma false, .swap_bytes true }, }; lv_disp_t *disp lvgl_port_add_disp(disp_cfg);所有改 UI 的代码必须包在lvgl_port_lock(0)/lvgl_port_unlock()之间包括从音频任务里更新电台名这种操作否则会在渲染中途改控件树表现为随机花屏或死机。4.2 LVGL 中 UI 更改分辨率时要同步改哪些地方分辨率不是编译期常量改起来有四层要一起动漏一层就是花屏或者触控偏移层级需要改的内容LVGL 显示驱动lv_disp_drv_t的hor_res/ver_res或显示配置结构里的 hres/vres屏幕控制器初始化命令里的行列地址范围ST7789 这类要改 CASET/RASET显示缓冲按新分辨率重算buffer_size别超过 PSRAM 可用量触摸触摸芯片的坐标范围和方向标志否则点击落在错位的控件上缓冲策略上无 PSRAM 的模组只能拿片内 SRAM 顶建议 20~40 行部分刷新加双缓冲带 PSRAM 的可以放大到半屏甚至全屏。240×320、16 位色下一块 40 行的缓冲约 30 KB双缓冲 60 KB片内 SRAM 还能接受。如果换成 480×320 的屏同样的行数就要 120 KB 以上就必须走 PSRAM 了。从 PC 上的 LVGL 模拟器或者用页面代码生成工具导出的界面搬到板子上时最容易忽略的就是这块模拟器里分辨率是写死在工程里的搬过来要手动对齐。4.3 lvgl 容器与页面组织用 tabview 装下两个收音机界面界面结构建议用 tabview 做顶层容器每个 tab 内部再放各自的控件树切换音源时只切 tab不用重建控件lv_obj_t *tv lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 36); lv_obj_t *tab_net lv_tabview_add_tab(tv, Net Radio); lv_obj_t *tab_fm lv_tabview_add_tab(tv, FM); /* 网络电台页列表 音量弧 */ lv_obj_t *list lv_list_create(tab_net); lv_obj_set_size(list, LV_PCT(100), LV_PCT(70)); lv_obj_add_event_cb(lv_list_add_btn(list, LV_SYMBOL_AUDIO, Station A), station_click_cb, LV_EVENT_CLICKED, (void *)http://your-host/a.mp3); lv_obj_t *arc lv_arc_create(tab_net); lv_arc_set_range(arc, 0, 100); lv_obj_align(arc, LV_ALIGN_BOTTOM_MID, 0, -8); /* FM 页频率标签 步进按钮 */ lv_obj_t *freq lv_label_create(tab_fm); lv_label_set_text(freq, 100.0 MHz); lv_obj_align(freq, LV_ALIGN_CENTER, 0, -20);事件回调里绝对不能直接调audio_pipeline_run或者写 I2C这类操作会阻塞几十甚至几百毫秒LVGL 任务卡住的表现就是按钮按下去没反应。正确做法是回调里往 FreeRTOS 消息队列丢一个结构体由音频控制任务消费后执行UI 只负责显示。4.4 单核下的任务优先级划分与 CPU 抢占S2 只有一个核LVGL 任务、音频 pipeline 的每个 element 各一个任务、WiFi 协议栈任务全挤在一起。优先级排错顺序音频会断UI 会顿。经验值如下任务优先级栈大小说明WiFi / LWIP18~23默认交给 IDF 默认值别动I2S 输出74096最高欠载就是爆音MP3 解码68192高于拉流低于输出HTTP 拉流54096受网络阻塞影响LVGL 端口26144低于所有音频任务FM 扫描13072只在用户操作时跑功耗和帧率之间要取舍时先降 LVGL 的刷新率和timer_period_ms别去降解码任务的优先级。真要定位是谁在占 CPU开CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS用vTaskGetRunTimeStats()打一张占比表出来看比凭感觉猜准得多。5. 联调排错与几个能省时间的技巧5.1 网络收音机断流、卡顿的排查顺序按这个顺序走基本三轮内能定位。第一看日志里http_stream有没有报 read timeout有就是网络层问题把out_rb_size和CONFIG_LWIP_TCP_WND_DEFAULT一起放大。第二看解码任务的状态如果反复在 RUNNING 和 FINISHED 之间跳多半是流的码率对不上把i2s_config.sample_rate改成 48000 试试。第三看idf.py monitor里有没有任务看门狗告警有就说明某个任务栈不够把对应 element 的task_stack加 2048 再看。第四才是怀疑 WiFi 信号用esp_wifi_sta_get_ap_info()把 RSSI 打出来低于 −75 dBm 时就别在软件上折腾了。wifi_ap_record_t ap; if (esp_wifi_sta_get_ap_info(ap) ESP_OK) { ESP_LOGI(TAG, rssi%d, channel%d, ap.rssi, ap.primary); }5.2 FM 无声、搜台失败的检查点先确认 I2C 通信本身通不通用i2c_master_probe扫地址扫不到就查上拉电阻和供电很多模块的使能脚要拉高才工作。地址扫到了但没声音读回 02H 确认 ENABLE 位写进去了读回时注意读地址和写地址差一。寄存器都正常还是没声音就看功放输入选择脚有没有切到 FM 这一路以及芯片的音频输出是否需要隔直电容才能接功放。搜台扫不到台先手动写一个已知存在的频点验证调谐通路再试自动搜台天线接一根 30 cm 左右的软线就有明显改善。5.3 长按按键清除配网信息的实现课设演示时经常要换 WiFi把 SSID 和密码存在 NVS 里配一个长按 3 秒擦除的逻辑最方便void factory_reset_check(void) { nvs_handle_t h; if (nvs_open(wifi_cfg, NVS_READWRITE, h) ESP_OK) { nvs_erase_all(h); /* 清掉这个命名空间下的全部键值 */ nvs_commit(h); nvs_close(h); } esp_wifi_disconnect(); esp_wifi_stop(); esp_restart(); /* 重启后进入未配置分支 */ }按键检测别用轮询加vTaskDelay(3000)那样会把整个任务堵死。用 GPIO 中断记录按下时刻在定时器里比对持续时间超过阈值再调用上面的函数同时往队列里丢一条「正在恢复出厂设置」的消息让 LVGL 弹个提示框。擦除只清自己那个命名空间别用nvs_flash_erase()把整个分区抹了校准数据和其他配置一起没下次上电射频参数要重新标定。本文还有配套的精品资源点击获取
返回列表