视觉与机电作品 / P4双芯片联网大屏实验发布 内容更新

ESP32-P4 加无线协处理器:把大屏看板跑起来

固定P4 v1.4/EK79007屏与板载C6,交付匹配的Hosted host/cp工程、自己的LAN数据页与整机复位恢复步骤。

读完能做什么
构建两个匹配固件,理解P4显示/网络与C6射频的边界,并按空表取得联网、触摸和复位恢复结果。
开始之前
先完成D06、E07、N03,能激活IDF5.5.5、区分两颗芯片/串口并保存原镜像;完整器材、逐针接线和命令在下载README。
所需器材
官方ESP32-P4-Function-EV-Board v1.4,确认P4旧0.x/1.x、16MB Flash及所选PSRAM容量;板上C6-MINI-1 N4/4MB、原套件7英寸1024×600 EK79007/GT911、LCD adapter、反向15针FPC、支柱与RST/PWM短线、P4 USB-to-UART数据线、LCD独立USB供电、ESP-Prog PROGRAM与匹配转接线(VDD不接)、自有2.4GHz WPA2/WPA3 AP、同LAN电脑与Python3,不需要云账号
版本组合
SDK / 工具:ESP-IDF5.5.5;复用RISC-V GCC14.2.0 esp-14.2.0_20260121两侧Hosted:Registry3.0.9 / 95e70156a3904fa172b9bb50888a4d7dd23d2f3aP4显示与包装:BSP5.2.3 / remote1.6.5 / LVGL9.4.0 / esp_lvgl_port2.9.0
适用范围

P4 Function EV Board v1.4/P4旧修订、16MB Flash/HEX PSRAM;1024×600 EK79007/GT911原套件、板载C6-MINI-1 N4。实体全pending。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/p4-display-sources.json;固定release/API鲜取、发布包/实际SDK比对、板图逐网,保留错误路径/归一化差异。验证日期:
构建 / 配置已通过
docs/esp32-evidence/p4-display-builds.json;最终两侧实际Exit0、原输入快照/14+14产品COW与日志SHA。首个host成功镜像留史。对象:P4旧修订host + C6 CP版本:ESP-IDF5.5.5验证日期:
仿真不适用
只运行共享策略/实际回调stub及真实localhost服务,没有P4/C6/Hosted/无线或面板仿真。
本站实板待验证
没有板/屏/ESP-Prog/无线/供电/触摸/复位结果;observations三个CSV仅表头。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / P4双芯片联网大屏实验

P4负责显示,C6才有无线射频

先把板型与网络拓扑确定下来:本例用官方P4 Function EV Board v1.4及其1024×600 EK79007/GT911屏套件,板上C6-MINI-1承担2.4GHz Wi-Fi。P4运行LVGL、TCP/IP和HTTP,读取自己电脑提供的合成LAB level,显示链路、最后有效值与数据年龄。没有传感器或云账号。

保留原计划V09、M6、原slug和用途,前置D06/E07/N03。当前交付包含两个真实可构建工程;网络、亮屏、触摸、CP恢复的实体结果仍为空。下面给出取得这些结果的完整步骤,不把主机服务或编译成功写成实板联网。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[2] v1.4官方原理图:第2/5页P4、SDIO、C6与J2[5] BSP5.2.3显示与触摸入口实现

02 / P4双芯片联网大屏实验

PCB版本、芯片修订和屏幕型号是三个条件

v1.4是PCB版本;它不等于P4芯片rev1.4。固定SDK对P4旧0.x/1.x与rev3.x支持互斥,本例选旧修订、16MB Flash及套件封装PSRAM(按32MB规格选配,启动日志再确认实际容量)。P4X、rev3.x及v1.5.2接口不在此例。板载C6按N4/4MB核模组标识和flash_id,不能仅凭MINI-1名称外推容量。

BSP5.2.3仍是preview,其README硬件表停在旧^3,默认屏描述也与实际Kconfig不一致。本文据发布包头/Kconfig和v1.4原厂接线映射,明确选择1024×600、RGB565、单帧,未宣称上游认证这一整套或本站拥有验证过的实物。LCD反向FPC不与摄像头同向线混用。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[2] v1.4官方原理图:第2/5页P4、SDIO、C6与J2[4] BSP5.2.3发布源Kconfig:实际屏型与帧配置[6] BSP5.2.3引脚与display配置结构[12] IDF5.5.5 P4修订支持互斥配置[17] C6-MINI-1原厂v1.5:N4与其他容量型号

03 / P4双芯片联网大屏实验

先看板内SDIO,再接LCD与电源

v1.4原理图第2/5页互核:P4 CLK18/CMD19/D0..3=14..17,对板载C6 CLK19/CMD18/D0..3=20..23;GPIO54经C6_EN到EN。板内有CMD/数据51kΩ上拉,不再用杜邦线重连。host固定slot1、4bit、20MHz;TF卡SD1的43/44/39..42不属于这条链路,GPIO53也只是音频PA控制。

断电装好原LCD adapter和支柱:J3接主板MIPI DSI,RST_LCD接GPIO27、PWM接GPIO26,触摸经套件I²C连接到SDA7/SCL8。LCD adapter用自己的USB 5V后,不再并接5V/GND电源跳线。主板用USB-to-UART供电/日志;每次改FPC、信号或烧录线前,两侧都断电。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[2] v1.4官方原理图:第2/5页P4、SDIO、C6与J2[3] 官方LCD adapter原理图[6] BSP5.2.3引脚与display配置结构

04 / P4双芯片联网大屏实验

先保存两颗芯片原镜像,再从正确口烧CP

首次会替换套件原CP固件,可能破坏原host的协议兼容性。先在私有目录备份两颗Flash并记容量、板/芯片修订、镜像SHA。生成镜像可能含凭据,不放下载包或公开日志。README给出只读chip_id/flash_id/read_flash命令;没有执行过这些实体读写的作者记录。

CP烧录前让P4留在ROM:按住主板BOOT,按RESET再松BOOT,先不运行host。ESP-Prog用PROGRAM接口,J2逐针为1 EN、2 NC、3 C6_TX、4 GND、5 C6_RX、6 C6_BOOT9。pin2不接VDD,按pin1与线序核对,信号3.3V。不要在host驱动GPIO54时再外拉EN;CP刷完断电移走程序线,再装屏、刷匹配host。完整表和恢复原镜像步骤在README。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[2] v1.4官方原理图:第2/5页P4、SDIO、C6与J2[16] ESP-Prog固定官方指南:PROGRAM接口与3.3V信号[17] C6-MINI-1原厂v1.5:N4与其他容量型号

05 / P4双芯片联网大屏实验

固定发布包,不用同标版本的后续main

本例复用IDF5.5.5。两侧正常Registry取esp_hosted3.0.9,其发布manifest指95e70156…;较后5c83bc7…的main也标3.0.9,不能混作同一字节基线。BSP5.2.3对应4b3f542…,remote1.6.5对应ad549729…,LVGL9.4.0/port2.9.0精确固定,各自dependencies.lock保留实际resolver结果。

只取必要正常Registry发行组件,没安装另一SDK/跑多平台bootstrap。BSP会解析video、codec、其他屏驱动,remote会解析EPPP依赖;下载这些依赖并不代表本应用调用摄像头、音频或EPPP。两份顶层工程完整独立,host与cp分开目录构建;不要把S3引导偏移或sdkconfig复制过来。

代码 / 配置
# ---- host/CMakeLists.txt ----
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(v09_p4_dashboard VERSION 1.0.0)
# ---- cp/CMakeLists.txt ----
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(v09_c6_coprocessor VERSION 1.0.0)

参考:[4] BSP5.2.3发布源Kconfig:实际屏型与帧配置[5] BSP5.2.3显示与触摸入口实现[7] Hosted3.0.9发布源:init与connect生命周期[9] Hosted3.0.9 constructor开关范围[11] wifi_remote1.6.5实际IDF包装选择

06 / P4双芯片联网大屏实验

配置三条总线的单位,启用代码仍不放真实凭据

host明确旧修订最低0.0,所以SDK实际Flash配置DIO40MHz;PSRAM独立是HEX16线/200MHz;SDIO又是20MHz。三个MHz不能混成一个“内存速度”。一帧原始像素1024×600×2=1228800B,LVGL draw buffer1024×32×2=65536B,只是像素预算,驱动/对齐/任务/网络还需内存,未测总heap或FPS。

按README在激活5.5.5的新终端,以新的绝对-B目录、IDF_TARGET与SDKCONFIG_DEFAULTS构建两侧。默认关顶层constructor,host未启用或占位即CONFIG_MISSING;cp默认CP_DISABLED。作者最终编译显式sdkconfig.lab启用路径,host占位仍不启动应用无线。未额外声称两个完整默认SDK构建。

参考:[4] BSP5.2.3发布源Kconfig:实际屏型与帧配置[9] Hosted3.0.9 constructor开关范围[12] IDF5.5.5 P4修订支持互斥配置[13] IDF5.5.5 P4 Flash频率限制[14] IDF5.5.5 P4 HEX PSRAM配置

07 / P4双芯片联网大屏实验

先在电脑启动自己的合成端点

运行下载中的python tools/status_server.py --bind 127.0.0.1 --port 8099 --mode valid,先验证本机;实体实验再把bind改成本机自己的LAN IPv4。--help列出valid/malformed/oversize/status503/redirect/slow;Ctrl-C停止后才能在同端口换输入。端点只返回level=0..100和LF,来源是电脑单调时间,不是温度或其他物理量。

host/sdkconfig.local.example复制为私有sdkconfig.local,填自己的2.4GHz WPA2/WPA3 SSID、8..63字节密码、该电脑LAN URL;默认允许SSID1..31字节。对P4而言127.0.0.1指P4自己,不能当电脑地址。用defaults+私有覆盖的新目录重新build后刷P4 UART口;本例局域网明文HTTP不传秘密,HTTPS排错另见R04。

代码 / 配置
# ---- tools/status_server.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Own-lab synthetic HTTP source. No sensor, no ESP communication proof."""
import argparse
import http.server
import time

class StatusHandler(http.server.BaseHTTPRequestHandler):
    mode = "valid"
    def do_GET(self):
        if self.path != "/status":
            self.send_error(404); return
        if self.mode == "redirect":
            self.send_response(302); self.send_header("Location", "/status"); self.end_headers(); return
        if self.mode == "status503":
            self.send_error(503); return
        if self.mode == "malformed": body = b"level=101\n"
        elif self.mode == "oversize": body = b"x" * 97
        else: body = f"level={int(time.monotonic()) % 101}\n".encode("ascii")
        self.send_response(200); self.send_header("Content-Type", "text/plain; charset=us-ascii")
        self.send_header("Content-Length", str(len(body))); self.send_header("Cache-Control", "no-store")
        self.end_headers()
        try:
            if self.mode == "slow":
                for byte in body:
                    self.wfile.write(bytes([byte])); self.wfile.flush(); time.sleep(0.5)
            else: self.wfile.write(body)
        except (BrokenPipeError, ConnectionResetError): pass
    def log_message(self, fmt, *args): pass

def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--bind", default="127.0.0.1", help="Use your own LAN IPv4 for an ESP test")
    parser.add_argument("--port", type=int, default=8099)
    parser.add_argument("--mode", choices=["valid","malformed","oversize","status503","redirect","slow"], default="valid")
    args = parser.parse_args()
    if not 0 <= args.port <= 65535: parser.error("port must be 0..65535")
    StatusHandler.mode = args.mode
    server = http.server.ThreadingHTTPServer((args.bind,args.port),StatusHandler)
    server.daemon_threads = True
    print(f"LAB_SYNTHETIC http://{args.bind}:{server.server_port}/status mode={args.mode}",flush=True)
    try: server.serve_forever()
    except KeyboardInterrupt: pass
    finally: server.server_close()

if __name__ == "__main__": main()

参考:[1] v1.4官方指南:串口、LCD反向线与供电[11] wifi_remote1.6.5实际IDF包装选择[15] IDF5.5.5 HTTP客户端超时与响应事件实现

08 / P4双芯片联网大屏实验

同源策略:好数据才更新接收时刻

下载中的策略头直接被固件和主机测试包含。只接受精确level=0..100加单个LF,拒绝CR、尾随空白、越界、嵌入NUL与额外内容。坏响应只加错误计数,保留上次好值和64位接收时间;超过10000ms显示STALE,从没接收过则NO SAMPLE。FRESH指接收年龄,不代表电脑数据是物理测量或仍在线。

重试使用esp_timer单调64位毫秒,2/4/8/16/30s封顶,连上后重置;没有把32位millis完整周期误当新鲜状态。解析/时间/配置/退避及饱和计数已有29组真实同源ASan/UBSan输入,范围只到应用策略。

代码 / 配置
// ---- host/main/dashboard_policy.h ----
// SPDX-License-Identifier: MIT
#ifndef V09_DASHBOARD_POLICY_H
#define V09_DASHBOARD_POLICY_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#define V09_BODY_CAP 96U
#define V09_STALE_MS UINT64_C(10000)

/* Exact lab wire format: level=0..100 followed by one LF. No physical sensor. */
static inline bool v09_parse_level(const char *p, size_t n, uint8_t *out)
{
    if (!p || !out || n < 8 || n > 10 || memcmp(p, "level=", 6) || p[n - 1] != '\n') return false;
    unsigned value = 0;
    for (size_t i = 6; i + 1 < n; ++i) {
        if (p[i] < '0' || p[i] > '9') return false;
        value = value * 10 + (unsigned)(p[i] - '0');
    }
    if (value > 100) return false;
    *out = (uint8_t)value;
    return true;
}

typedef struct {
    bool has_value;
    uint8_t value;
    uint64_t received_ms;
    uint32_t accepted;
    uint32_t rejected;
} v09_sample_t;

static inline void v09_accept(v09_sample_t *s, uint8_t value, uint64_t now)
{
    s->has_value = true; s->value = value; s->received_ms = now;
    if (s->accepted != UINT32_MAX) ++s->accepted;
}
static inline void v09_reject(v09_sample_t *s)
{
    if (s->rejected != UINT32_MAX) ++s->rejected;
}
static inline bool v09_fresh(const v09_sample_t *s, uint64_t now)
{
    return s->has_value && now >= s->received_ms && now - s->received_ms <= V09_STALE_MS;
}
static inline bool v09_config_ready(bool enabled, const char *ssid, const char *password, const char *url)
{
    if (!enabled || !ssid || !password || !url) return false;
    return strlen(ssid) >= 1 && strlen(ssid) <= 31 && strlen(password) >= 8 && strlen(password) <= 63 &&
           strlen(url) < 192 && strncmp(url, "http://", 7) == 0 &&
           !strstr(ssid, "REPLACE_") && !strstr(password, "REPLACE_") && !strstr(url, "REPLACE_");
}
static inline uint64_t v09_deadline(uint64_t now, uint64_t delay)
{
    return UINT64_MAX - now < delay ? UINT64_MAX : now + delay;
}
typedef struct { uint64_t due_ms; unsigned delay_s; } v09_retry_t;
static inline void v09_retry_reset(v09_retry_t *r) { r->due_ms = 0; r->delay_s = 2; }
static inline void v09_retry_fail(v09_retry_t *r, uint64_t now)
{
    if (r->delay_s < 2 || r->delay_s > 30) r->delay_s = 2;
    r->due_ms = v09_deadline(now, (uint64_t)r->delay_s * 1000);
    r->delay_s = r->delay_s >= 16 ? 30 : r->delay_s * 2;
}
static inline bool v09_retry_due(const v09_retry_t *r, uint64_t now) { return now >= r->due_ms; }
#endif

参考:[15] IDF5.5.5 HTTP客户端超时与响应事件实现

09 / P4双芯片联网大屏实验

完整P4应用:UI与网络有各自的任务

下面是完整host main。网络任务独占应用Wi-Fi/Hosted/HTTP调用,SDK回调只复制最新状态;同轮lost再got以最后链接状态为准。transport failure/down则在本boot粘性锁住,迟到GOT_IP不能改回IP-ready。HTTP完成后再抓一次链接快照,不接受已知离线/transport故障后的返回。

HTTP只接受200、最多96B正文、禁自动重定向。3000ms是网络操作超时,不是perform端到端总deadline,慢分块可更久。UI每250ms独立更新年龄,恢复控制又在另一任务;不会把慢HTTP等同于屏幕绘制停止。应用连接窗口15s和射频内部重试不是同一计数,失败不自动擦NVS。

代码 / 配置
// ---- host/main/main.c ----
// SPDX-License-Identifier: MIT
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_hosted.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "bsp/esp-bsp.h"
#include "lvgl.h"
#include "dashboard_policy.h"

#ifndef CONFIG_APP_ENABLE_NETWORK
#define CONFIG_APP_ENABLE_NETWORK 0
#endif

static const char *TAG = "v09";
typedef struct { bool online, transport_failed; char ip[16]; uint32_t cp_boot_notices; } link_t;
typedef struct { char phase[72]; v09_sample_t sample; } view_t;
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
static link_t s_link;
static view_t s_view = { .phase = "Display starting; network not started" };
static QueueHandle_t s_restart;
static lv_obj_t *s_phase, *s_link_label, *s_value, *s_stats;

static uint64_t now_ms(void) { return (uint64_t)esp_timer_get_time() / 1000; }
static link_t link_snapshot(void)
{
    portENTER_CRITICAL(&s_lock); link_t out = s_link; portEXIT_CRITICAL(&s_lock); return out;
}
static void publish(const char *phase, const v09_sample_t *sample)
{
    portENTER_CRITICAL(&s_lock);
    if (phase) snprintf(s_view.phase, sizeof(s_view.phase), "%s", phase);
    if (sample) s_view.sample = *sample;
    portEXIT_CRITICAL(&s_lock);
}

/* Callback copies state only. Network APIs belong to network_task. */
static void event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    (void)arg;
    portENTER_CRITICAL(&s_lock);
    if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP && data) {
        ip_event_got_ip_t *got = data;
        s_link.online = !s_link.transport_failed && got->ip_info.ip.addr != 0;
        if (s_link.online) snprintf(s_link.ip, sizeof(s_link.ip), IPSTR, IP2STR(&got->ip_info.ip));
        else s_link.ip[0] = 0;
    } else if ((base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) ||
               (base == IP_EVENT && id == IP_EVENT_STA_LOST_IP)) {
        s_link.online = false; s_link.ip[0] = 0;
    } else if (base == ESP_HOSTED_EVENT &&
               (id == ESP_HOSTED_EVENT_TRANSPORT_FAILURE || id == ESP_HOSTED_EVENT_TRANSPORT_DOWN)) {
        s_link.transport_failed = true; s_link.online = false; s_link.ip[0] = 0;
    } else if (base == ESP_HOSTED_EVENT && id == ESP_HOSTED_EVENT_CP_INIT) {
        if (s_link.cp_boot_notices != UINT32_MAX) ++s_link.cp_boot_notices;
    }
    portEXIT_CRITICAL(&s_lock);
}

typedef struct { char body[V09_BODY_CAP]; size_t used; bool overflow; } body_t;
static esp_err_t http_event(esp_http_client_event_t *e)
{
    body_t *b = e->user_data;
    if (e->event_id == HTTP_EVENT_ON_DATA && e->data_len > 0) {
        if ((size_t)e->data_len > sizeof(b->body) - b->used) { b->overflow = true; return ESP_FAIL; }
        memcpy(b->body + b->used, e->data, (size_t)e->data_len); b->used += (size_t)e->data_len;
    }
    return ESP_OK;
}
static bool fetch_level(uint8_t *value)
{
    body_t body = {0};
    const esp_http_client_config_t cfg = {
        .url = CONFIG_APP_STATUS_URL, .timeout_ms = 3000, .buffer_size = 512,
        .event_handler = http_event, .user_data = &body, .disable_auto_redirect = true,
    };
    esp_http_client_handle_t h = esp_http_client_init(&cfg);
    if (!h) return false;
    const esp_err_t result = esp_http_client_perform(h);
    const int status = esp_http_client_get_status_code(h);
    const bool ok = result == ESP_OK && status == 200 && !body.overflow &&
                    v09_parse_level(body.body, body.used, value);
    esp_http_client_cleanup(h);
    return ok;
}

/* This task owns every application Wi-Fi/Hosted/HTTP API. No API in callbacks/UI. */
static void network_task(void *arg)
{
    (void)arg;
    v09_sample_t sample = {0};
    if (!v09_config_ready(CONFIG_APP_ENABLE_NETWORK, CONFIG_APP_WIFI_SSID,
                          CONFIG_APP_WIFI_PASSWORD, CONFIG_APP_STATUS_URL)) {
        publish("CONFIG_MISSING: fill private sdkconfig.local; network stays off", NULL);
        vTaskDelete(NULL); return;
    }
    esp_err_t result = nvs_flash_init();
    if (result != ESP_OK) { publish("NVS init failed; stopped without erase", NULL); goto stopped; }
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, event, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(ESP_HOSTED_EVENT, ESP_EVENT_ANY_ID, event, NULL));
    if (!esp_netif_create_default_wifi_sta()) { publish("STA netif allocation failed", NULL); goto stopped; }
    publish("SDIO: starting C6; fixed startup strategy resets CP", NULL);
    if (esp_hosted_init() != 0 || esp_hosted_connect_to_slave() != 0) {
        publish("CP bring-up failed; check pair/power/bus, then restart both", NULL); goto stopped;
    }
    esp_hosted_coprocessor_fwver_t version = {0}; uint32_t chip = 0; char target[16] = {0};
    if (esp_hosted_get_coprocessor_fwversion(&version) != ESP_OK ||
        esp_hosted_get_cp_info(&chip, target, sizeof(target)) != ESP_OK ||
        version.major1 != 3 || version.minor1 != 0 || version.patch1 != 9 || strcmp(target, "esp32c6")) {
        publish("CP identity/version mismatch or RPC failed; Wi-Fi not started", NULL); goto stopped;
    }
    ESP_LOGI(TAG, "CP %s, chip=%" PRIu32 ", Hosted=%" PRIu32 ".%" PRIu32 ".%" PRIu32,
             target, chip, version.major1, version.minor1, version.patch1);
    wifi_init_config_t init = WIFI_INIT_CONFIG_DEFAULT();
    result = esp_wifi_init(&init);
    if (result != ESP_OK) { publish("Wi-Fi init RPC failed; restart both", NULL); goto stopped; }
    wifi_config_t wifi = {0};
    snprintf((char *)wifi.sta.ssid, sizeof(wifi.sta.ssid), "%s", CONFIG_APP_WIFI_SSID);
    snprintf((char *)wifi.sta.password, sizeof(wifi.sta.password), "%s", CONFIG_APP_WIFI_PASSWORD);
    wifi.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
    if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK || esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK ||
        esp_wifi_set_config(WIFI_IF_STA, &wifi) != ESP_OK || esp_wifi_start() != ESP_OK) {
        publish("Wi-Fi setup RPC failed; stopped, no automatic erase", NULL); goto stopped;
    }
    v09_retry_t retry; v09_retry_reset(&retry);
    bool connecting = false, previously_online = false;
    uint64_t connect_until = 0, poll_at = 0;
    for (;;) {
        /* Take event snapshot before time. The callback's latest link state wins. */
        link_t link = link_snapshot(); uint64_t now = now_ms();
        if (link.transport_failed) { publish("CP transport failed; restart P4 + reset C6 to recover", &sample); goto stopped; }
        if (link.online) {
            if (!previously_online) { v09_retry_reset(&retry); connecting = false; poll_at = now; }
            previously_online = true;
            if (now >= poll_at) {
                uint8_t value = 0; const bool fetched = fetch_level(&value);
                link = link_snapshot(); now = now_ms();
                if (fetched && link.online && !link.transport_failed) {
                    v09_accept(&sample, value, now); publish("ONLINE: valid lab level received", &sample);
                } else { v09_reject(&sample); publish("HTTP/link error: old value retained with age", &sample); }
                poll_at = v09_deadline(now, 3000);
            }
        } else {
            if (previously_online) { previously_online = false; connecting = false; v09_retry_fail(&retry, now); }
            if (connecting && now >= connect_until) {
                connecting = false; v09_retry_fail(&retry, now); publish("No IP after app window; backing off", &sample);
            }
            if (!connecting && v09_retry_due(&retry, now)) {
                /* Stop a previous attempt before starting the next; callbacks never call connect. */
                (void)esp_wifi_disconnect();
                if (esp_wifi_connect() == ESP_OK) {
                    connecting = true; connect_until = v09_deadline(now_ms(), 15000);
                    publish("CONNECTING: waiting for non-zero DHCP address", &sample);
                } else { v09_retry_fail(&retry, now_ms()); publish("Connect RPC failed; backing off", &sample); }
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
stopped:
    ESP_LOGW(TAG, "network task stopped; display/restart control remain available");
    vTaskDelete(NULL);
}

/* Restart runs independently of a potentially blocked network operation.
 * This intentionally restarts the P4 as well. Hosted startup then resets C6.
 * No application GPIO write races the driver's ownership of GPIO54. */
static void restart_task(void *arg)
{
    (void)arg; uint8_t command;
    if (xQueueReceive(s_restart, &command, portMAX_DELAY) == pdTRUE) {
        publish("Restart requested: P4 reboots, then Hosted resets C6", NULL);
        vTaskDelay(pdMS_TO_TICKS(300)); esp_restart();
    }
    vTaskDelete(NULL);
}
static void request_restart(lv_event_t *e)
{
    (void)e; const uint8_t command = 1; (void)xQueueSend(s_restart, &command, 0);
}
static void refresh_ui(lv_timer_t *timer)
{
    (void)timer;
    portENTER_CRITICAL(&s_lock); view_t view = s_view; link_t link = s_link; portEXIT_CRITICAL(&s_lock);
    const uint64_t now = now_ms();
    lv_label_set_text(s_phase, view.phase);
    lv_label_set_text_fmt(s_link_label, "C6 radio / P4 TCP-IP | %s | IP %s | CP boot notices %" PRIu32,
                          link.online ? "IP ready" : "OFFLINE", link.ip[0] ? link.ip : "--", link.cp_boot_notices);
    if (!view.sample.has_value) lv_label_set_text(s_value, "LAB level: -- / 100 | NO SAMPLE");
    else lv_label_set_text_fmt(s_value, "LAB level: %u / 100 | %s | age %" PRIu64 " ms",
                              (unsigned)view.sample.value, v09_fresh(&view.sample, now) ? "FRESH" : "STALE",
                              now >= view.sample.received_ms ? now - view.sample.received_ms : 0);
    lv_label_set_text_fmt(s_stats, "accepted %" PRIu32 " | errors %" PRIu32 " | uptime %" PRIu64 " s\n"
                         "HTTP data is synthetic. No sensor / FPS / throughput measurement.",
                         view.sample.accepted, view.sample.rejected, now / 1000);
}
static lv_obj_t *label_at(lv_obj_t *root, int y)
{
    lv_obj_t *label = lv_label_create(root); lv_obj_set_width(label, 960); lv_obj_set_pos(label, 24, y);
    lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); return label;
}
void app_main(void)
{
    s_restart = xQueueCreate(1, sizeof(uint8_t));
    if (!s_restart || xTaskCreate(restart_task, "v09_restart", 3072, NULL, 6, NULL) != pdPASS) {
        ESP_LOGE(TAG, "restart control allocation failed; network not started"); return;
    }
    bsp_display_cfg_t cfg = {
        .lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG(), .buffer_size = 1024 * 32,
        .double_buffer = false, .hw_cfg = { .hdmi_resolution = BSP_HDMI_RES_NONE,
            .dsi_bus = { .phy_clk_src = 0, .lane_bit_rate_mbps = BSP_LCD_MIPI_DSI_LANE_BITRATE_MBPS } },
        .flags = { .buff_dma = true, .buff_spiram = false, .sw_rotate = false },
    };
    lv_display_t *display = bsp_display_start_with_config(&cfg);
    if (!display) { ESP_LOGE(TAG, "display/touch init failed; network not started"); return; }
    if (!bsp_display_lock(2000)) { ESP_LOGE(TAG, "LVGL lock failed; network not started"); return; }
    lv_obj_t *root = lv_screen_active();
    lv_obj_set_style_bg_color(root, lv_color_hex(0x101c2d), 0);
    lv_obj_set_style_text_color(root, lv_color_hex(0xe8f2ff), 0);
    lv_obj_t *title = label_at(root, 28); lv_label_set_text(title, "V09 | P4 display + C6 wireless");
    s_phase = label_at(root, 104); s_link_label = label_at(root, 160);
    s_value = label_at(root, 235); s_stats = label_at(root, 310);
    lv_obj_t *button = lv_button_create(root); lv_obj_set_pos(button, 24, 438); lv_obj_set_size(button, 650, 82);
    lv_obj_t *caption = lv_label_create(button); lv_label_set_text(caption, "Restart P4 + reset C6 (both will reconnect)"); lv_obj_center(caption);
    lv_obj_add_event_cb(button, request_restart, LV_EVENT_CLICKED, NULL);
    lv_timer_create(refresh_ui, 250, NULL); refresh_ui(NULL); bsp_display_unlock();
    if (bsp_display_brightness_set(30) != ESP_OK) { ESP_LOGE(TAG, "backlight failed; network not started"); return; }
    if (xTaskCreate(network_task, "v09_network", 8192, NULL, 5, NULL) != pdPASS)
        publish("Network task allocation failed; display still available", NULL);
}

参考:[5] BSP5.2.3显示与触摸入口实现[7] Hosted3.0.9发布源:init与connect生命周期[8] Hosted3.0.9启动CP复位与故障策略[9] Hosted3.0.9 constructor开关范围[11] wifi_remote1.6.5实际IDF包装选择[15] IDF5.5.5 HTTP客户端超时与响应事件实现

10 / P4双芯片联网大屏实验

完整C6应用:先检查NVS,再由host给Wi-Fi命令

cp只在APP_ENABLE_COPROCESSOR启用时初始化NVS、默认事件循环和eh_cp_init。constructor已关闭,NVS失败直接停止不erase;两侧使用同一3.0.9发布组件、MCU RPC模式和板内SDIO。CP不是另一个HTTP客户端,应用数据/面板仍在P4。

host启动经SDK带起/复位C6后,实际RPC核Hosted版本3.0.9与目标esp32c6,通过才配置Wi-Fi;不能只看一个连接成功日志就把错配固件算可用。这个读回是版本/目标检查,不是密码学身份认证,也没有证明某个实体曾完成RPC。

代码 / 配置
// ---- cp/main/main.c ----
// SPDX-License-Identifier: MIT
#include "sdkconfig.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "eh_cp.h"

void app_main(void)
{
#if !CONFIG_APP_ENABLE_COPROCESSOR
    ESP_LOGW("v09_cp", "CP_DISABLED: enable sdkconfig.lab only after checking this board/pair");
    return;
#else
    const esp_err_t nvs = nvs_flash_init();
    if (nvs != ESP_OK) {
        ESP_LOGE("v09_cp", "NVS init failed (%s); stopped without erasing", esp_err_to_name(nvs));
        return;
    }
    const esp_err_t events = esp_event_loop_create_default();
    if (events != ESP_OK && events != ESP_ERR_INVALID_STATE) {
        ESP_LOGE("v09_cp", "event loop failed: %s", esp_err_to_name(events));
        return;
    }
    const esp_err_t started = eh_cp_init();
    ESP_LOGI("v09_cp", "Hosted3.0.9 CP init=%s; Wi-Fi commands/data come from the P4 host",
             esp_err_to_name(started));
#endif
}

参考:[7] Hosted3.0.9发布源:init与connect生命周期[8] Hosted3.0.9启动CP复位与故障策略[9] Hosted3.0.9 constructor开关范围[10] Hosted3.0.9版本头与CP RPC回包

11 / P4双芯片联网大屏实验

恢复按钮重启P4,再由启动策略复位C6

屏按钮准确标注Restart P4 + reset C6。点击入单槽队列,重复请求可合并;独立控制任务延迟300ms后esp_restart,P4与显示都会重启。新host经Hosted ALWAYS启动策略驱动GPIO54复位C6,再核版本和联网。没有直接抢驱动reset GPIO,没有承诺CP-only热恢复、无缝显示或保留RAM样本。

先验正常亮屏/取值,再停服务看HTTP错误、NO SAMPLE/STALE;恢复服务等新好样本。屏上恢复或主板实体RESET后,核CP启动日志与新值。CP故障输入可在断电后换自己CP_DISABLED镜像再上电,停止并恢复正确镜像;不要短接活动EN或带电拔FPC。显示/触摸初始化失败则不启动网络;BSP默认error-check可能assert/panic,屏幕未必有提示,先保存UART并断电核前提,保留实体RESET后路。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[2] v1.4官方原理图:第2/5页P4、SDIO、C6与J2[5] BSP5.2.3显示与触摸入口实现[7] Hosted3.0.9发布源:init与connect生命周期[8] Hosted3.0.9启动CP复位与故障策略

12 / P4双芯片联网大屏实验

主机测试、本地TCP和实板记录分别保存

README给出实际可重跑命令:tests/host.c直接包含策略;tests/service_test.py运行真实localhost HTTP客户端/服务器后调用同源C解析器,5种输入只valid通过;tests/callback_body.py从当前main完整提取回调,在明确stub下跑11组事件/正文边界。它们不运行Hosted、SDIO、Wi-Fi射频或屏驱动。

作者自查先保首个成功host镜像与原回调观察,再修迟到GOT_IP的标签边界、按SDK旧修订要求明确Flash40M,重新实际构建作为最终host。原源/日志/14产物保留,非把初始SDK成功直接当业务通过。三个observations CSV只有表头,等待具体板、供电、触摸点与恢复时间等实测。

参考:[7] Hosted3.0.9发布源:init与connect生命周期[8] Hosted3.0.9启动CP复位与故障策略[13] IDF5.5.5 P4 Flash频率限制[15] IDF5.5.5 HTTP客户端超时与响应事件实现

13 / P4双芯片联网大屏实验

以两侧同次源、配置、镜像和空表作为交付边界

最终真实host app1312736B、cp app1132352B,各14件产品及同次输入/日志SHA可查作者证据p4-display-builds.json;输入在原staging构建,随后逐字冻结,未声称从ready重新构建。主机结果与版本锁已核,实板显示/触摸/供电/SDIO/无线/复位仍pending。

把两份原镜像、实际板号与模组容量核对齐,再用README逐层取得数据;若NVS失败、版本RPC不对或供电前提不符就停在该层。继续学习可回看D06交互、E07依赖锁、N03客户端和R04 TLS;这里不扩成视觉模型、第三方云或无线性能结论。

参考:[1] v1.4官方指南:串口、LCD反向线与供电[7] Hosted3.0.9发布源:init与connect生命周期[11] wifi_remote1.6.5实际IDF包装选择[15] IDF5.5.5 HTTP客户端超时与响应事件实现

BACK TO THE SOURCE

资料来源与版本说明

保留原V09/slug/M6用途;按真实发布包commit而非同标版本main固定。BSP硬件表/默认屏描述滞后,依据v1.4指南、板图与实际Kconfig映射,不冒称整套获官方认证。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. v1.4官方指南:串口、LCD反向线与供电
  2. v1.4官方原理图:第2/5页P4、SDIO、C6与J2
  3. 官方LCD adapter原理图
  4. BSP5.2.3发布源Kconfig:实际屏型与帧配置
  5. BSP5.2.3显示与触摸入口实现
  6. BSP5.2.3引脚与display配置结构
  7. Hosted3.0.9发布源:init与connect生命周期
  8. Hosted3.0.9启动CP复位与故障策略
  9. Hosted3.0.9 constructor开关范围
  10. Hosted3.0.9版本头与CP RPC回包
  11. wifi_remote1.6.5实际IDF包装选择
  12. IDF5.5.5 P4修订支持互斥配置
  13. IDF5.5.5 P4 Flash频率限制
  14. IDF5.5.5 P4 HEX PSRAM配置
  15. IDF5.5.5 HTTP客户端超时与响应事件实现
  16. ESP-Prog固定官方指南:PROGRAM接口与3.3V信号
  17. C6-MINI-1原厂v1.5:N4与其他容量型号