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显示与触摸入口实现
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与其他容量型号
先看板内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配置结构
先保存两颗芯片原镜像,再从正确口烧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与其他容量型号
固定发布包,不用同标版本的后续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包装选择
配置三条总线的单位,启用代码仍不放真实凭据
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配置
先在电脑启动自己的合成端点
运行下载中的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客户端超时与响应事件实现
同源策略:好数据才更新接收时刻
下载中的策略头直接被固件和主机测试包含。只接受精确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
完整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客户端超时与响应事件实现
完整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回包
恢复按钮重启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复位与故障策略
主机测试、本地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客户端超时与响应事件实现
以两侧同次源、配置、镜像和空表作为交付边界
最终真实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客户端超时与响应事件实现
资料来源与版本说明
保留原V09/slug/M6用途;按真实发布包commit而非同标版本main固定。BSP硬件表/默认屏描述滞后,依据v1.4指南、板图与实际Kconfig映射,不冒称整套获官方认证。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。
- v1.4官方指南:串口、LCD反向线与供电
- v1.4官方原理图:第2/5页P4、SDIO、C6与J2
- 官方LCD adapter原理图
- BSP5.2.3发布源Kconfig:实际屏型与帧配置
- BSP5.2.3显示与触摸入口实现
- BSP5.2.3引脚与display配置结构
- Hosted3.0.9发布源:init与connect生命周期
- Hosted3.0.9启动CP复位与故障策略
- Hosted3.0.9 constructor开关范围
- Hosted3.0.9版本头与CP RPC回包
- wifi_remote1.6.5实际IDF包装选择
- IDF5.5.5 P4修订支持互斥配置
- IDF5.5.5 P4 Flash频率限制
- IDF5.5.5 P4 HEX PSRAM配置
- IDF5.5.5 HTTP客户端超时与响应事件实现
- ESP-Prog固定官方指南:PROGRAM接口与3.3V信号
- C6-MINI-1原厂v1.5:N4与其他容量型号