这次回答“在我的环境下怎样选”,先不给胜负
住处2.4GHz拥挤时,换到5GHz可能改变干扰、可用信道和穿墙表现;单凭芯片支持双频无法推断你的房间里一定更远、更快。本任务把同一台C5限制在单一频段,向同一台LAN电脑发送同样24字节消息,再按点位成对比较成功率、应用RTT和受控重连。
本站没有C5实板和AP测量。下面交付的是可执行实验方法与已经构建的源码;CSV只含表头,图中也不填预造的距离、RSSI或“提升百分比”。本页不跑最大吞吐、不测功耗,不把2.4GHz上的ESP-NOW/LR功能扩展成5GHz结论。
先核对C5板型,收起原来S3的引脚设置
固定官方C5-DevKitC-1-N8R8/WROOM-1 PCB天线,指南v1.2的PW-2025-04-0446及以后,电路V1.2。该模组有8MB Flash和8MB PSRAM;工程用8MB DIO80、关闭PSRAM以保持变量简单,但GPIO15依然连接模组PSRAM CS,绝不是可以拿来接LED的空脚。
本例不接GPIO。只用标UART的USB-C桥口、保持J5跳帽;桥TX/RX是GPIO11/12,BOOT是GPIO28,RGB在GPIO27。USB这一类供电、5V排针供电、3V3排针供电互斥,本实验只选USB,不能接着USB再并一个排针电源。
| 需要固定 | 为什么要记录 |
|---|---|
| 同一台双频AP与固件/天线朝向 | 不同AP、功率和朝向会混入频段以外变量 |
| 两个独立SSID、同鉴权策略与20MHz | 避免band steering偷偷换频段/宽度 |
| 有线接LAN的电脑、固定IPv4 | 减少电脑无线链路和DNS对往返测量的影响 |
| 真实国家/实际AP country、信道 | API允许的配置与设备实际关联信息分别保留 |
- 做到这里,你应该看到
- 板上型号/后缀、指南PW范围、USB桥口与两个AP SSID均可说清楚。
参考:[1] C5官方指南v1.2:N8R8/PW、GPIO15、供电与桥口[2] C5电路V1.2(2025-02-11)p2
用两个目录构建,默认凭据为空时不启动无线
复用ESP-IDF5.5.5和匹配RISC-V14.2.0工具;main/idf_component.yml/dependencies.lock锁定目标,工程没有额外组件。main/config.c单独保存占位SSID/密码/两字符国家码/电脑IPv4;默认空值使CONFIG_DISABLED在初始化Wi-Fi之前返回。把凭据留在本地,不提交到公开站点。
先填2.4GHz SSID并构建build-2g,再换5GHz SSID构建build-5g。两个镜像都固定HT20与PS_NONE。set_band_mode要求Wi-Fi已经start,所以回调不在STA_START抢先connect,主任务设置频段后才开始连接。编译常量2/5限制频段,不表示同时利用两频段传包。
country_code必须对应真实使用地区;802.11d=true时关联AP的国家信息可以取代本地值,断开后恢复。本API文档明确国家配置会写Flash,不能因为连接配置用了WIFI_STORAGE_RAM就称所有配置都不会保存。记录AP日志中的实际country/policy/channel,遇到不能关联先检查AP地区/鉴权条件,不通过假国家码解锁信道。
idf.py -B build-2g -DSDKCONFIG="$PWD/build-2g/sdkconfig" -DW08_BAND=2 build
idf.py -B build-2g -DSDKCONFIG="$PWD/build-2g/sdkconfig" -DW08_BAND=2 -p YOUR_C5_UART_PORT flash monitor
# 另填5GHz SSID后:
idf.py -B build-5g -DSDKCONFIG="$PWD/build-5g/sdkconfig" -DW08_BAND=5 build
idf.py -B build-5g -DSDKCONFIG="$PWD/build-5g/sdkconfig" -DW08_BAND=5 -p YOUR_C5_UART_PORT flash monitor参考:[3] IDF5.5.5 C5 Wi-Fi:功能/事件、IP后建socket、断线重建[4] 固定esp_wifi.h:set_band_mode启动前提、国家设置/802.11d
完整主循环:IP、socket和ACK分别确认
下载包同时包含下面五个原文件、CMake/版本锁、完整服务/分析器和README。链路状态更新与取快照使用同一临界区;快照消费标记不会清掉一个并发更新。IP generation变化时重建socket,即使一次短暂断线和重连都发生在主循环两次观察之间,也不沿用旧socket。
得到IP之后才建UDP socket;断线/IP更新会关闭并在有效IP下重建。RSSI信息查询时连接可能再次变化,因此AP_RECORD_UNAVAILABLE只记日志,不把可恢复的瞬时状态变成abort。应用尝试15s窗口与最高16s退避都用64位单调时间;日志中的reason保留驱动原值。
DATA中64位boot与seq区分当前运行和请求,ACK还要来自指定电脑IP/端口并回显发送时间等字段。错误/旧包继续等到窗口结束;这不是安全认证,也不是单向PHY时延测量。500ms是应用检查窗口,系统调度不是硬实时上界。
// ---- main/config.h ----
// SPDX-License-Identifier: MIT
#pragma once
extern const char w08_ssid[],w08_password[],w08_country[],w08_echo_ip[];
// ---- main/config.c ----
// SPDX-License-Identifier: MIT
#include "config.h"
// Set locally. Do not commit credentials. Each band has its own AP SSID; use the same AP hardware/security/channel width.
const char w08_ssid[]="";
const char w08_password[]="";
// Your actual operating country ISO code, for example CN only when operating in China. Empty disables the experiment.
const char w08_country[]="";
// IPv4 of the wired computer running tools/echo_server.py on the same LAN; no DNS dependency.
const char w08_echo_ip[]="";
// ---- main/w08_link.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct {bool online,disconnected,lost_ip;uint64_t generation;} W08Link;
static inline void w08_got_ip(W08Link *s){s->online=true;s->generation++;}
static inline void w08_disconnected(W08Link *s){s->online=false;s->disconnected=true;}
static inline void w08_lost_ip(W08Link *s){s->online=false;s->lost_ip=true;}
// Caller holds the same critical section used for updates; consuming flags cannot erase a concurrent update.
static inline W08Link w08_snapshot(W08Link *s){W08Link r=*s;s->disconnected=false;s->lost_ip=false;return r;}
// ---- main/w08_wire.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define W08_SIZE 24
static inline void w08_data(uint8_t p[W08_SIZE],uint64_t boot,uint32_t seq,uint64_t sent_us){
memcpy(p,"W08D",4);
for(unsigned i=0;i<8;i++){p[4+i]=(uint8_t)(boot>>(56-i*8));p[16+i]=(uint8_t)(sent_us>>(56-i*8));}
for(unsigned i=0;i<4;i++)p[12+i]=(uint8_t)(seq>>(24-i*8));
}
static inline int w08_ack(uint8_t *out,const uint8_t *in,size_t n){
if(n!=W08_SIZE || memcmp(in,"W08D",4))return 0;
memcpy(out,in,W08_SIZE);out[3]='A';return 1;
}
static inline int w08_match(const uint8_t *ack,size_t n,const uint8_t expected[W08_SIZE]){
return n==W08_SIZE && !memcmp(ack,"W08A",4) && !memcmp(ack+4,expected+4,W08_SIZE-4);
}
// ---- main/main.c ----
// SPDX-License-Identifier: MIT
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include "lwip/inet.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "config.h"
#include "w08_wire.h"
#include "w08_link.h"
#ifndef W08_BAND
#define W08_BAND 2
#endif
static W08Link link_state;
static portMUX_TYPE link_mux=portMUX_INITIALIZER_UNLOCKED;
static const char *TAG="W08";
static uint64_t mono(void){return (uint64_t)esp_timer_get_time();}
static void event(void *arg,esp_event_base_t base,int32_t id,void *data){
(void)arg;
if(base==WIFI_EVENT && id==WIFI_EVENT_STA_DISCONNECTED){
const wifi_event_sta_disconnected_t *d=data;
portENTER_CRITICAL(&link_mux);w08_disconnected(&link_state);portEXIT_CRITICAL(&link_mux);
ESP_LOGW(TAG,"DISCONNECT mono_us=%" PRIu64 " reason=%u",mono(),d->reason);
}else if(base==IP_EVENT && id==IP_EVENT_STA_GOT_IP){
portENTER_CRITICAL(&link_mux);w08_got_ip(&link_state);portEXIT_CRITICAL(&link_mux);
ESP_LOGI(TAG,"GOT_IP mono_us=%" PRIu64,mono());
}else if(base==IP_EVENT && id==IP_EVENT_STA_LOST_IP){
portENTER_CRITICAL(&link_mux);w08_lost_ip(&link_state);portEXIT_CRITICAL(&link_mux);
}
}
static void ap_record(void){
wifi_ap_record_t a={0};wifi_country_t c={0};wifi_band_mode_t mode;
const esp_err_t ret=esp_wifi_sta_get_ap_info(&a);
if(ret!=ESP_OK){ESP_LOGW(TAG,"AP_RECORD_UNAVAILABLE ret=%s (connection may have changed)",esp_err_to_name(ret));return;}
ESP_ERROR_CHECK(esp_wifi_get_country(&c));ESP_ERROR_CHECK(esp_wifi_get_band_mode(&mode));
ESP_LOGI(TAG,"AP mono_us=%" PRIu64 " selected=%dG mode=%d channel=%u rssi_dbm=%d phy_11ax=%u country=%.3s policy=%d bssid=%02x:%02x:%02x:%02x:%02x:%02x",mono(),W08_BAND,mode,a.primary,a.rssi,a.phy_11ax,c.cc,c.policy,a.bssid[0],a.bssid[1],a.bssid[2],a.bssid[3],a.bssid[4],a.bssid[5]);
}
static void probe(int sock,const struct sockaddr_in *peer,uint64_t boot,uint32_t seq){
uint8_t tx[W08_SIZE],rx[128];const uint64_t started=mono();
w08_data(tx,boot,seq,started);
const int sent=sendto(sock,tx,sizeof tx,0,(const struct sockaddr*)peer,sizeof *peer);
bool ack=false;
while(sent==(int)sizeof tx && mono()-started<500000){
fd_set fds;FD_ZERO(&fds);FD_SET(sock,&fds);struct timeval tv={.tv_sec=0,.tv_usec=50000};
if(select(sock+1,&fds,NULL,NULL,&tv)<=0)continue;
struct sockaddr_in from={0};socklen_t len=sizeof from;
const int n=recvfrom(sock,rx,sizeof rx,0,(struct sockaddr*)&from,&len);
if(n<0)break;
if(from.sin_addr.s_addr!=peer->sin_addr.s_addr || from.sin_port!=peer->sin_port)continue;
if(w08_match(rx,(size_t)n,tx)){ack=true;break;}
}
ESP_LOGI(TAG,"PROBE mono_us=%" PRIu64 " boot=%016" PRIx64 " seq=%" PRIu32 " ack=%d elapsed_us=%" PRIu64 " send_errno=%d",mono(),boot,seq,ack,mono()-started,sent<0?errno:0);
}
void app_main(void){
struct sockaddr_in peer={.sin_family=AF_INET,.sin_port=htons(45680)};
if(!w08_ssid[0] || !w08_password[0] || strlen(w08_country)!=2 || inet_pton(AF_INET,w08_echo_ip,&peer.sin_addr)!=1){
ESP_LOGW(TAG,"CONFIG_DISABLED: set actual SSID/password/country/echo IPv4 in main/config.c; no Wi-Fi initialized");return;
}
if(strlen(w08_ssid)>32 || strlen(w08_password)<8 || strlen(w08_password)>63){ESP_LOGE(TAG,"CONFIG_INVALID: WPA2/WPA3 personal passphrase, SSID <=32 bytes");return;}
ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,event,NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT,ESP_EVENT_ANY_ID,event,NULL));
wifi_init_config_t init=WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&init));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_country_code(w08_country,true));
wifi_config_t cfg={0};memcpy(cfg.sta.ssid,w08_ssid,strlen(w08_ssid));memcpy(cfg.sta.password,w08_password,strlen(w08_password));
cfg.sta.threshold.authmode=WIFI_AUTH_WPA2_PSK;cfg.sta.pmf_cfg.capable=true;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&cfg));ESP_ERROR_CHECK(esp_wifi_start());
// set_band_mode requires started Wi-Fi. STA_START handler intentionally never calls connect.
ESP_ERROR_CHECK(esp_wifi_set_band_mode(W08_BAND==5?WIFI_BAND_MODE_5G_ONLY:WIFI_BAND_MODE_2G_ONLY));
ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_STA,WIFI_BW_HT20));ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
const uint64_t boot=((uint64_t)esp_random()<<32)|esp_random();
int sock=-1;uint64_t socket_generation=0;
bool attempting=false,was_online=false;uint64_t deadline=0,next_try=0,next_probe=0;uint32_t backoff_ms=1000,seq=0;
for(;;){
portENTER_CRITICAL(&link_mux);const W08Link bits=w08_snapshot(&link_state);portEXIT_CRITICAL(&link_mux);
if(bits.disconnected || bits.lost_ip){
if(sock>=0){close(sock);sock=-1;}
attempting=false;next_try=mono()+(uint64_t)backoff_ms*1000;
// A later GOT_IP in this snapshot supersedes the earlier LOST_IP.
if(bits.lost_ip && !bits.online)(void)esp_wifi_disconnect();
if(backoff_ms<16000)backoff_ms*=2;
}
const uint64_t now=mono();const bool online=bits.online;
if(online){
attempting=false;backoff_ms=1000;
if(!was_online || bits.generation!=socket_generation){
if(sock>=0){close(sock);sock=-1;}
socket_generation=bits.generation;ap_record();next_probe=now;
}
if(now>=next_probe){
if(sock<0)sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sock>=0)probe(sock,&peer,boot,++seq);
else ESP_LOGE(TAG,"SOCKET_FAILED errno=%d; resource failure is not an RF coverage result",errno);
next_probe=mono()+2000000;
}
}else{
if(was_online){attempting=false;next_try=now;backoff_ms=1000;}
if(attempting && now>=deadline){attempting=false;(void)esp_wifi_disconnect();next_try=now+(uint64_t)backoff_ms*1000;ESP_LOGW(TAG,"CONNECT_TIMEOUT");}
if(!attempting && now>=next_try){
const esp_err_t ret=esp_wifi_connect();attempting=ret==ESP_OK;deadline=now+15000000;
if(!attempting){next_try=now+(uint64_t)backoff_ms*1000;if(backoff_ms<16000)backoff_ms*=2;}
ESP_LOGI(TAG,"CONNECT_TRY mono_us=%" PRIu64 " ret=%s",now,esp_err_to_name(ret));
}
}
was_online=online;vTaskDelay(pdMS_TO_TICKS(20));
}
}
参考:[3] IDF5.5.5 C5 Wi-Fi:功能/事件、IP后建socket、断线重建[4] 固定esp_wifi.h:set_band_mode启动前提、国家设置/802.11d
先让近点两频段都跑通同一台电脑服务
在同LAN电脑启动下面的stdlib服务,绑定真实LAN IPv4。允许本地防火墙UDP45680,关闭该测试SSID客户端隔离;没有公网端口或云服务。服务LISTEN只证明电脑在监听,必须实际收到合法DATA才会写ACK行。
同一轮快照若已依次收到LOST_IP→GOT_IP,仍关闭旧socket,但不会因旧LOST_IP标志额外断开新关联;只有快照仍offline时才请求disconnect。处理以该次快照为界,不能排除之后的无线变化。
实板成功检查同时看板端GOT_IP、所选频段的AP记录、PROBE ack=1,以及电脑相同boot/seq的ACK。仅GOT_IP不能证明服务收到;仅服务进程启动也不能证明无线。本站实际跑过的是localhost回环服务,不是这两频段实板检查。
# ---- tools/echo_server.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""LAN UDP echo endpoint, not evidence that a board connected. Bind an explicit local IPv4."""
import argparse,socket,struct,time
def ack(data):
return b"W08A"+data[4:] if len(data)==24 and data[:4]==b"W08D" else None
def main():
p=argparse.ArgumentParser();p.add_argument("--bind",required=True);p.add_argument("--port",type=int,default=45680);a=p.parse_args()
with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as s:
s.bind((a.bind,a.port));print("LISTEN",s.getsockname(),flush=True)
while True:
data,peer=s.recvfrom(2048);reply=ack(data)
if reply is None:print("REJECT",peer,len(data),flush=True);continue
s.sendto(reply,peer)
boot,seq,mono=struct.unpack("!QIQ",data[4:])
print("ACK",time.monotonic_ns(),peer,f"{boot:016x}",seq,mono,flush=True)
if __name__=="__main__":main()
- 做到这里,你应该看到
- 近点两频段各自有真实双方对应记录后,才开始移动位置采集。
固定点位、完整保留30条,再单独测断线恢复
固定AP/供电/方向、20MHz与鉴权条件,给每个位置编号并记录距离/隔墙。每到一个点或切换频段先RESET重新关联,取得此点的AP/RSSI和新boot;随后每频段连续记录30条PROBE,至少3次trial,轮换2G先/5G先的顺序。中途重启就开始新trial,不混同一组boot。
每个trial/点位/频段另从本次首次CONNECT_TRY的mono_us起固定观察120秒,在association.csv保留no_ip、ip_no_ack或ip_ack;仅真实发生的GOT_IP/首ACK才填时间;no_ip没有PROBE,boot也留空,以trial/point/频段及复位日志标识本次。没有IP也必须留下失败行,不能删去这些点后宣布覆盖更好。CONFIG_DISABLED/错误目标/socket资源失败先修环境,不算射频失败。
每一失败探测也占一个分母位置。ACK0的RTT留空,RSSI抄本次关联记录,不冒充每包即时RSSI。CSV写实际镜像SHA、boot、信道、AP实物id、距离/障碍;同AP的两个频段BSSID可不同,原始AP日志保留。
近点分别关闭AP无线10秒再开启,保留DISCONNECT→GOT_IP→新的ACK。本文重连时间从驱动报告断开算到重新获得IP,不包含驱动尚未发现的失联时间;操作者开关时刻单独注记。恢复IP后仍需新ACK,不能把连上路由器当作业务恢复。
- 做到这里,你应该看到
- 每个点两频段的成功和失败行都在,且记录的是同环境、同条数的独立trial。
分析器先拒绝坏记录,不用空表推出性能结论
先运行tools/association.py分析固定120秒可用性表;它拒绝空表、非固定窗口、未成对/不同AP、非有限时间和ACK早于IP,no_ip不给虚构时间。两个频段不能都取得探测时,不计算RTT胜负。
运行python3 tools/compare.py records/coverage.csv。工具拒绝空表、缺一频段、不同环境、混固件/boot、重复或缺失序号、不同条数、NaN/Inf、失败行却填RTT等;只有输入一致才给已关联条件下的探测成功率、成功包RTT中位数、关联RSSI中位数和重连列表。全失败时RTT是null,不能拿零代替。
“5GHz是否值得”应写成你的点位/业务门槛判断:先规定最低成功率和可接受应用延迟,再看各位置是否达标;RSSI不是距离计,也不把一轮差异泛化到其他AP/住处。phy_11ax只是AP记录能力字段,不证明每个包都使用ax调制。分析器能拒坏格式,不能证明人工填入的数据来自实物。
# ---- tools/compare.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Analyze operator records. No generated observations; headers-only input is an error."""
import argparse,csv,json,math,re,statistics
def number(s,low,high):
v=float(s)
if not math.isfinite(v) or not low<=v<=high:raise ValueError("nonfinite/out-of-range number")
return v
def analyze(path):
groups={};seen=set()
with open(path,newline="",encoding="utf-8") as f:
for row in csv.DictReader(f):
band=row['band']
if band not in ('2','5'):raise ValueError('band must be 2 or 5')
trial,point,ap=row['trial'],row['point'],row['ap_id']
if not all((trial,point,ap,row['obstacle'])):raise ValueError('record environment identifiers')
if not re.fullmatch('[0-9a-f]{64}',row['firmware_sha256']):raise ValueError('firmware SHA256')
if not re.fullmatch('[0-9a-f]{16}',row['boot']):raise ValueError('boot 16 lowercase hex')
if row['width_mhz']!='20':raise ValueError('this comparison fixes 20 MHz')
ch=int(row['channel'])
if not (1<=ch<=14 if band=='2' else 32<=ch<=177):raise ValueError('channel/band mismatch; this does not certify regional legality')
distance=number(row['distance_m'],0,100000)
seq=int(row['seq'])
if not 1<=seq<=0xffffffff:raise ValueError('seq range')
key=(trial,point,band,seq)
if key in seen:raise ValueError('duplicate record')
seen.add(key)
if row['ack'] not in ('0','1'):raise ValueError('ack binary')
ack=row['ack']=='1'
rtt=number(row['rtt_ms'],0,float('inf')) if ack else None
if not ack and row['rtt_ms']:raise ValueError('failed probe must not invent RTT')
rssi=number(row['rssi_dbm'],-127,0)
reconnect=number(row['reconnect_ms'],0,86400000) if row['reconnect_ms'] else None
k=(trial,point,band)
g=groups.setdefault(k,{'environment':(ap,distance,row['obstacle']),'firmware':row['firmware_sha256'],'boot':row['boot'],'rows':[]})
if g['environment']!=(ap,distance,row['obstacle']) or g['firmware']!=row['firmware_sha256'] or g['boot']!=row['boot']:raise ValueError('mixed environment/firmware inside group')
g['rows'].append((seq,ack,rtt,rssi,reconnect))
if not groups:raise ValueError('no observations; blank template is not a result')
out=[]
for trial,point in sorted({(t,p) for t,p,b in groups}):
a,b=groups.get((trial,point,'2')),groups.get((trial,point,'5'))
if a is None or b is None:raise ValueError('pair both bands at every trial/point')
if a['environment']!=b['environment'] or len(a['rows'])!=len(b['rows']):raise ValueError('unequal environment/probe count')
for band,g in [('2',a),('5',b)]:
seqs=sorted(x[0] for x in g['rows'])
if seqs!=list(range(seqs[0],seqs[0]+len(seqs))):raise ValueError('missing probe rows')
good=[x[2] for x in g['rows'] if x[1]]
out.append({'trial':trial,'point':point,'band':band,'attempts':len(seqs),'acked':len(good),'success_fraction':len(good)/len(seqs),'median_rtt_ms':statistics.median(good) if good else None,'median_rssi_dbm':statistics.median(x[3] for x in g['rows']),'reconnect_ms':[x[4] for x in g['rows'] if x[4] is not None]})
return out
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('csv');a=p.parse_args()
print(json.dumps(analyze(a.csv),ensure_ascii=False,indent=2,allow_nan=False))
分清配置错误、LAN错误和真正的无线覆盖失败
CONFIG_DISABLED/INVALID先修本地配置;没有GOT_IP先读reason,再核频段SSID/国家/AP鉴权/DHCP,最后移动近点。已经GOT_IP但没有ACK,先检查电脑IPv4、服务、防火墙和客户端隔离。SOCKET_FAILED是资源故障,不计作射频覆盖失败;重连中的AP查询失败也不填假RSSI。
USB桥找不到先换数据线/驱动;按住BOOT28再按RESET进下载模式,重刷esp32c5目标。最后手段erase-flash会清NVS等数据,只有明确选择此板恢复时使用,之后重新填实际配置。
本轮两种最终镜像实际构建Exit0,同源帧/链路快照11组、探测表16组/关联表11组合成输入和真实localhost UDP5组通过;未执行C5关联、双频覆盖、断线恢复、手机或电流实验。先修复前置N01/L02的方法基础,再把本页自己的记录留存;若要转向无路由器通信,可回读已发布ESP-NOW教程。
资料来源与版本说明
官方网页与具体固定源码逐项核对,详见docs/esp32-evidence/wireless-c5-c6.md及sources/builds/hosts JSON。硬件和无线链路没有本站观测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。