1. 为什么智能车调试要上WiFi图传:先想清楚再动手
1.1 没有图传时,调车到底痛在哪
这几年带队的经历让我有一个很深的感触:智能车能不能跑得快,一半看调参,另一半看你怎么“看得见”车上的数据。我们在赛道边最常干的事,就是把车拎回来、插上串口线、打开逐飞虚拟示波器,然后对着波形和图像一帧一帧地翻。
这个流程刚入门的时候还能忍,等车真正跑起来,速度一上去,痛点就全出来了:
- 车跑完一圈,摄像头图像到底是入弯时丢线了,还是出弯时补线补歪了?只能靠车上的SD卡回放,但很多同学根本没接SD卡;
- 想实时看灰度图像,串口线不够长,车一出去线就得拔下来,拔下来以后就成了“盲调”;
- 车撞了、翻了一圈,首要的就是确认摄像头是否还正常,可人跑过去之前摄像头到底是什么状态,你永远不知道。
WiFi图传解决的就是这个“最后一米”的问题。把主控板采集到的原始图像数据通过无线发回电脑端,你站在赛道外面,就能像看监控一样实时盯着摄像头画面。这也算是一种远程调试,能帮你把调车效率提上来一个台阶。
1.2 常见的三种图传方案该怎么选
我先给新手朋友们排个雷:给智能车做图传,市面上能走的路线其实就那么几条,但能用在“逐飞主控板 + 逐飞摄像头”组合上的方案,并不是所有都合适。
方案A:直接买现成的WiFi图传模块,比如某些支持WiFi输出的摄像头模块。这类模块很多自带MCU和摄像头,直接接电池供电,然后用手机或电脑看画面。优点是非常省事,缺点也很明显:它跟主控板的图像数据完全无关,你看的是“另一个摄像头”的画面,而不是主控板真正在用的那路图像。调车时如果图像处理逻辑出问题,这种图传根本帮不上忙。
方案B:用逐飞官方/山外那套无线调试产品。这属于“官方路线”,有配套上位机,稳定性好,但需要额外买硬件,而且有些协议细节是封装好的,想自己改一改图像压缩方式、分辨率、封装格式时会觉得受限。
方案C:自己移植,用主控板现有的总钻风/OV7725摄像头,把采集到的图像通过串口发给ESP8266/ESP32,再由ESP8266以WiFi透明传输方式发到电脑。这个方案实现成本低、代码完全可控,而且能真正看到主控板正在处理的图像。缺点是需要自己写一点协议和上位机,但也就是一两天工作量的事。
我下面要展开的就是方案C。它的移植思路和代码,基本可以平移到逐飞的RT1064、TC264、TC377这些常见主控板上,只要串口和摄像头接口对应得上就行。
1.3 移植之前,先明确这套方案的数据流向
很多同学一拿到代码就急着编译下载,结果连信号从哪到哪都没搞清楚,出了问题根本无从下手。我先用一句话把整套流程串起来:
摄像头(总钻风) → 主控板(逐飞核心板,采集一帧灰度图) → 主控板内部做尺寸缩小和帧格式封装 → UART串口 → ESP8266(WiFi透明传输) → 电脑端的TCP服务程序 → 上位机显示画面。
注意,这里ESP8266扮演的是“串口转WiFi的桥”,它本身不处理图像,只负责把串口收到的原始字节流原封不动地发到电脑。所以只要电脑端能收到一个稳定字节流,剩下的工作全在主控板和上位机两边。
这种做法的好处是图像处理逻辑完全不受WiFi影响,你可以直接在电脑上看到总钻风采集到的每一帧原始灰度图像,然后判断线上的二值化阈值、丢线判断逻辑是否靠谱。
2. 硬件准备与接线:别让一根线毁掉一次调车
2.1 物料清单
先把要用的东西摆出来,基本就下面这些:
| 物料 | 型号/说明 | 数量 |
|---|---|---|
| 主控板 | 逐飞RT1064核心板/TC264等,只要带UART和摄像头接口就行 | 1 |
| 摄像头 | 逐飞总钻风灰度摄像头(OV7725),188×120灰度输出 | 1 |
| WiFi模块 | ESP8266-01S或NodeMCU开发板,推荐用NodeMCU方便供电和调试 | 1 |
| 串口调试助手 | 电脑端,用于先单独调试ESP8266 | 1 |
| 杜邦线/排针 | 若干 | |
| 面包板 | 可选,方便接线 |
我建议新手直接用NodeMCU版本的ESP8266,因为上面自带了USB转串口芯片和稳压电路,不用外接电平转换,也方便先用电脑把AT指令调通。等整套流程验证完,再考虑换更小的ESP-01嵌入车模。
2.2 摄像头到主控板的连接
逐飞总钻风摄像头用的是软排线接口,直接插到核心板对应的摄像头接口上就行。注意插的时候金色触点朝下,插到位以后能听到“咔哒”一声。这里容易出现一个低级失误:排线插反或者只插进去一半,导致初始化失败,图像全黑或者花屏。
插好之后,在主控板代码里确认摄像头初始化对应的接口。RT1064上用的通常是DVP接口加DMA,逐飞SDK里camera_init()会自动把引脚配置好,不需要自己连一根根信号线。
2.3 主控板到ESP8266的UART接线与电平匹配
这块要仔细看。ESP8266的串口电平是3.3V,逐飞主控板的串口引脚电平也是3.3V,所以正常情况下可以直接连。但如果你用的是5V供电的NodeMCU开发板,引脚电平还是3.3V,依然没问题。
接线对应关系如下:
| 主控板UART引脚 | ESP8266引脚 | 说明 |
|---|---|---|
| TX | RX | 主控板发送,ESP8266接收 |
| RX | TX | ESP8266发送,主控板接收 |
| GND | GND | 必须共地,否则数据全是乱的 |
| 5V/3.3V | VCC | 推荐外接稳定5V给开发板,见2.4 |
我自己踩过一个大坑:只接了TX、RX,忘记共地,结果串口打印全部是乱码,纠结了好久才反应过来。串口通信本质上是看两个设备之间的电平差,地都不通,电平差就是漂移的,数据不可能对。
2.4 电源分配建议
ESP8266在WiFi发射瞬间的电流尖峰可以达到300mA以上,如果直接从主控板3.3V引脚取电,很容易把主控板电压拉低,导致主控板复位。这个“WiFi一发射,主控板就重启”的问题非常经典。
我的建议是:
- 如果用的是NodeMCU开发板,直接给它接一个独立的5V电源(比如电池经过稳压模块出来的5V),让它自己板载稳压到3.3V;
- 如果用的是裸ESP-01模块,供电走3.3V,但一定要在模块电源脚旁边并联一个100uF~470uF的电解电容和0.1uF的陶瓷电容,滤掉瞬时压降;
- 主控板和ESP8266之间,串口电平转换一般不需要,但如果发现ESP8266收不到数据,优先检查VCC电压是否稳定,而不是怀疑引脚接错。
3. 图像数据量估算与协议设计:为什么直接发整帧会卡死
3.1 总钻风图像的原始数据量
总钻风摄像头输出的是灰度图,分辨率是188×120,每个像素用1字节表示灰度值0~255。那么:
$$188 \times 120 = 22560 \text{ 字节/帧}$$
如果完全不压缩、不缩小,直接通过串口发一整帧,我们来看看需要多大的带宽。
3.2 串口波特率下的理论帧率
串口发送是按字节算的,波特率921600时,加上起始位和停止位,每秒实际最多传约92160字节。那么理论帧率是:
$$92160 / 22560 \approx 4.1 \text{ 帧/秒}$$
这个帧率实际上只能算“幻灯片级别”,而且还要算上采集图像、打包协议、WiFi转发丢包重传等开销,实际能到2帧/秒就不错了。如果是115200波特率,那理论帧率只有0.5帧/秒,基本不可用。
所以我建议先做两件事:
- 把图像缩小:每两列取一列、每两行取一行,得到94×60的灰度图,数据量降到 $94 \times 60 = 5640$ 字节/帧;
- 把波特率拉高:直接设置到921600,保证瓶颈不在串口上。
缩小之后的帧率估算如下表:
| 波特率 | 有效数据速率 | 94×60灰度图理论帧率 | 实际可参考帧率 |
|---|---|---|---|
| 115200 | 约11520 B/s | 2.0 帧/秒 | 1~2 帧/秒 |
| 460800 | 约46080 B/s | 8.2 帧/秒 | 5~7 帧/秒 |
| 921600 | 约92160 B/s | 16.3 帧/秒 | 10~14 帧/秒 |
实际调车时,10帧左右的灰度图已经足够看清赛道元素和丢线情况了。如果你后面想进一步提高帧率,可以把图像灰度量化为4bit甚至二值化,数据量还能再砍一半到四分之一。
3.3 帧协议设计:帧头+长度+数据+校验
无线传输不像有线串口那么稳定,TCP还好一些,如果以后改用UDP就会丢包。所以我们必须给数据设计一个简单的帧格式,让电脑端能在一堆字节流中认出“一帧图像从哪里开始、到哪里结束”。
我用的是一个非常经典的协议格式:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 0xAA | 帧头1 |
| 1 | 0x55 | 帧头2 |
| 2 | 高度 | 缩小后的图像高度,比如60 |
| 3 | 宽度 | 缩小后的图像宽度,比如94 |
| 4 ~ 4+W×H-1 | 图像数据 | 灰度像素按行排列 |
| 末尾 | 校验和 | 前面所有字节累加,取低8位 |
帧头用0xAA 0x55是为了让接收端能快速找到起点。宽度和高度字段一定不能省,否则接收端不知道一帧到底有多大。校验和用最原始的累加和,虽然不如CRC结实,但对于调试阶段完全够用,而且算起来快。
我见过不少同学一上来就整CRC32,把单片机端代码写得很复杂,结果采集一帧的时间还没发出去的时间长。调试阶段的协议,够用就好,等稳定了再升级。
4. 主控板端代码移植:逐飞SDK基础上一步步改
4.1 摄像头初始化:把官方例程的取图逻辑先跑通
在动手改WiFi相关代码之前,先确保你的摄像头单独跑官方例程能出图。我不知道你用的具体是哪款逐飞核心板,但流程基本一致:
- 用逐飞官方例程创建一个工程,确认摄像头能正常初始化;
- 在循环里调用采集函数,把图像数据打印到LCD或虚拟示波器上看一眼;
- 确认图像是完整的188×120灰度图,没有花屏、割裂、偏色。
RT1064上的初始化代码看起来就像这样:
#include "headfile.h" #define IMAGE_W 188 #define IMAGE_H 120 static uint8 image_data[IMAGE_H][IMAGE_W]; int main(void) { clock_init(SYSTEM_CLOCK_600M); debug_init(); camera_init(); // 初始化总钻风摄像头 while (1) { if (camera_get_frame(&image_data[0][0], IMAGE_W, IMAGE_H) == 0) { continue; } // 到这里,image_data 里就是一帧完整的灰度图像 } }需要说明的是,camera_get_frame的具体返回值和参数在不同版本的逐飞SDK里可能略有差别,有的版本是直接在回调里置一个结束标志位。不管用哪种方式,核心逻辑都一样:等摄像头DMA把一帧搬运完,再去处理图像,不要在采集到一半时去读写图像缓冲区。
这里有个新手容易忽略的问题:我们缩小的图像发送,最好直接用原始图像缓冲区在内存里原地抽取,而不是先拷贝一份再缩小。原因很简单,逐飞主控板带摄像头DMA时,通常会使用双缓冲,缓冲区可能同时被摄像头硬件写入。你如果不小心把正在被DMA写入的区域拿去读,就会出现“上半帧是新的,下半帧是旧的”这种割裂画面。
4.2 图像数据缩放:从188×120降到适合无线传输的尺寸
我采用的是最简单的等间隔抽点,也就是每两行取一行、每两列取一列。虽然这会丢失一些细节,但总钻风的灰度图本身信息量足够,抽点后94×60依然能清晰看到赛道边缘、斑马线和十字路口。
下面这个函数的作用,就是把原始188×120图像抽成94×60输出到发送缓冲里:
#define SEND_W 94 #define SEND_H 60 static uint8 send_buf[4 + SEND_W * SEND_H + 1]; static uint16_t pack_frame(uint8 *out, uint8 *img, uint16_t img_w, uint16_t img_h) { uint16_t index = 0; uint16_t i, j; uint8_t sum = 0; // 帧头、高度、宽度 out[index++] = 0xAA; out[index++] = 0x55; out[index++] = SEND_H; out[index++] = SEND_W; // 等间隔抽点 for (i = 0; i < img_h; i += 2) { for (j = 0; j < img_w; j += 2) { out[index++] = img[i * img_w + j]; } } // 累加和校验 for (i = 0; i < index; i++) { sum += out[i]; } out[index++] = sum; return index; }这里要注意,抽点时img_w和img_h必须是偶数,否则循环越界。总钻风是188×120,本来就是偶数,所以没问题。如果你以后换了别的摄像头,最好在函数开头加断言。
想保留更多图像细节的话,也可以改成隔两行取两行、隔两列取两列,数据量会翻一倍,帧率相应下降,自己权衡。
4.3 串口初始化与发送:选好UART口,注意波特率上下限
主控板端最关键的配置就是串口。以RT1064为例,一般用UART4或者UART5作为调试串口,具体引脚要查逐飞的引脚分配表。我习惯把WiFi图传单独放一个UART,不要和调试打印共用,免得图像数据把调试信息冲掉。
初始化代码:
#define WIFI_UART UART_4 // 根据自己板子和SDK选择 #define WIFI_BAUD 921600 void wifi_uart_init(void) { uart_init(WIFI_UART, WIFI_BAUD); }发送一帧时,直接把打包好的send_buf发出去:
uint16_t frame_len = pack_frame(send_buf, &image_data[0][0], IMAGE_W, IMAGE_H); uart_putbuff(WIFI_UART, send_buf, frame_len);有些逐飞SDK版本里串口发送函数的名字可能不一样,比如uart_write_buffer,没关系,换成你SDK里对应的接口就行。只要最终效果是“把一整块buffer的字节按顺序发出去”就对了。
我建议不要在主循环里用uart_putchar一个一个字节地发,因为每个字符函数调用都有开销,921600波特率下可能中间出现断流。尽量用SDK提供的批量发送接口,一次性把整帧丢给串口硬件DMA。
4.4 主循环整合:采集、打包、发送的状态机
整合之后的主循环比大家想象的要简单。逐飞摄像头一般用DMA连续采集,所以主循环只需要检查“这一帧是否已经准备好了”,然后打包发送即可:
while (1) { if (camera_get_frame(&image_data[0][0], IMAGE_W, IMAGE_H) == 0) { continue; } uint16_t frame_len = pack_frame(send_buf, &image_data[0][0], IMAGE_W, IMAGE_H); uart_putbuff(WIFI_UART, send_buf, frame_len); }别小看这个循环,它已经是一个完整的“采集-发送”状态机了。真正做到后面,如果帧率不够,就要开始排查什么地方耗时最长。我实测下来,90%的耗时都花在uart_putbuff等待串口发送完成上,因为921600波特率下,5640字节一帧发完需要大约60ms。这个时间没办法完全消除,除非你改成DMA发送,让CPU在DMA搬运数据的同时去跑图像处理算法。
5. ESP8266配置与串口透传:把AT指令玩明白
5.1 先用电脑单独调通ESP8266,别一上来就连主控板
很多同学栽在ESP8266上,原因是把ESP8266接到主控板上之后,串口助手就连不上了。所以我强烈建议先用USB转串口模块把ESP8266单独插到电脑上,用串口助手发AT指令验证一遍。
NodeMCU开发板本身带USB转串口,插上电脑,打开设备管理器确认COM口号,波特率设成115200,然后输入AT,如果返回OK,说明模块正常。
ESP8266默认波特率是115200。因为我们要跟主控板跑921600,所以需要用AT指令把波特率改掉:
AT+UART_DEF=921600,8,1,0,0改完之后,串口助手的波特率也要同步改成921600,再发AT验证。这一步很容易忘了同步电脑端波特率导致误判。
5.2 配置WiFi连接与TCP Client
接下来把ESP8266设置为Station模式,连接你的路由器或者手机热点:
AT+CWMODE=1 AT+CWJAP="你的WiFi名称","你的WiFi密码"如果连接成功,会返回WIFI CONNECTED,然后OK。这里有几点经验:
- 手机热点名称里尽量不要有中文和特殊字符,ESP8266对SSID编码支持一般;
- 手机热点建议选2.4GHz频段,ESP8266不支持5GHz;
- 电脑和ESP8266必须连同一个WiFi,不然TCP连不上。
然后配置TCP客户端,连接电脑上运行的Python上位机:
AT+CIPMUX=0 AT+CIPSTART="TCP","192.168.1.100",8080这里192.168.1.100换成你电脑的IP地址。查看电脑IP的方法很简单,Windows下在命令行输入ipconfig,找到无线网卡对应的IPv4地址;Linux下用ifconfig或ip addr。
5.3 透传模式和单片机对接
TCP连接成功之后,输入下面的指令进入透传模式:
AT+CIPMODE=1 AT+CIPSEND进入透传模式后,ESP8266会返回一个>提示符,之后所有从串口进来的数据都会被原封不动转发到TCP服务器。也就是说,主控板往串口发什么,电脑端就能收到什么。
这里有一个关键点:透传模式一旦开启,AT指令就失效了,你需要发送“+++”退出透传模式,才能重新发AT指令。所以调试时,建议先在串口助手验证完整流程,再接到主控板上。
接上主控板之前,记得把ESP8266的串口TX/RX和主控板的UART交叉连接,然后重新上电。主控板端开机后,ESP8266需要几秒时间去连接WiFi并建立TCP,在此之前主控板发过来的数据会被丢掉。所以我的建议是主控板上电后延时2~3秒再开始发图像,或者在上电时判断一下ESP8266是否已经进入透传状态。
6. PC端接收与显示:写一个最简Python上位机
6.1 环境准备:Python + OpenCV
电脑端我用Python写了一个最简上位机,只需要安装两个库:
pip install opencv-python numpy不依赖逐飞官方上位机,也不依赖任何商业软件,代码就一百来行。运行起来后,脚本会开启一个TCP服务端,ESP8266连上来之后就能直接开始收图像。
6.2 TCP服务端接收与组帧
接收端的难点在于:图像数据是一个接一个的字节流,可能在中间断开,也可能一帧数据分好几次到达。所以必须先收进缓冲区,再从缓冲区里按协议把完整帧找出来。
下面这版代码我已经在RT1064+总钻风的组合上跑过,可以直接用:
import socket import cv2 import numpy as np HOST = "0.0.0.0" # 监听所有网卡 PORT = 8080 SEND_W = 94 SEND_H = 60 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((HOST, PORT)) server.listen(1) print("[等待] ESP8266 连接...") conn, addr = server.accept() print("[已连接]", addr) buf = b"" frame_len = 2 + 2 + SEND_W * SEND_H + 1 # 帧头2 + 高宽2 + 数据 + 校验1 while True: data = conn.recv(4096) if not data: print("[断开] 连接已断开") break buf += data index = 0 while len(buf) - index >= frame_len: if (buf[index] == 0xAA and buf[index + 1] == 0x55 and buf[index + 2] == SEND_H and buf[index + 3] == SEND_W): packet = buf[index:index + frame_len] # 校验和验证 s = 0 for b in packet[:-1]: s = (s + b) & 0xFF if s == packet[-1]: # 提取图像数据并显示 img = np.frombuffer(packet[4:4 + SEND_W * SEND_H], dtype=np.uint8) img = img.reshape(SEND_H, SEND_W) img_scale = cv2.resize(img, (SEND_W * 4, SEND_H * 4), interpolation=cv2.INTER_NEAREST) cv2.imshow("WiFi Image", img_scale) if cv2.waitKey(1) & 0xFF == 27: # 按ESC退出 break index += frame_len continue index += 1 # 丢弃已经处理完和无法匹配的数据 buf = buf[index:] conn.close() server.close() cv2.destroyAllWindows()这段代码最关键的地方是内层的while循环:它一遍遍扫描缓冲区,找到一个合法的帧头,就尝试按帧长度截取完整帧,校验和通过就显示,不通过就往右移动一个字节继续找。这样即使一帧数据中间被WiFi的传输延迟切断了,也能在下一轮循环里把剩余部分拼回来。
显示的时候,因为94×60太小,我用cv2.resize把它放大4倍显示,方便看细节。放大用INTER_NEAREST保持像素边缘锐利,不会把图像搞糊。
6.3 实时显示与FPS统计
把代码跑起来以后,如果一切正常,你会看到一个灰度窗口,显示主控板摄像头看到的画面。想确认帧率的话,可以临时加一个计数器:
frame_count = 0 last_time = time.time() # 在每次显示一帧后: frame_count += 1 if frame_count % 30 == 0: now = time.time() fps = frame_count / (now - last_time) print(f"FPS: {fps:.2f}") last_time = now frame_count = 0注意电脑端要关掉防火墙对Python进程的拦截,否则ESP8266可能连不上TCP端口。Windows弹防火墙提示时,一定要选择“允许访问”。
7. 实测效果与排错心得:从“出图像”到“稳定图传”
7.1 实测参数参考
我在逐飞RT1064核心板 + 总钻风摄像头上实测,使用921600波特率、94×60灰度图、NodeMCU作为WiFi桥,距离10米以内、无遮挡时,帧率稳定在10~13帧/秒,图像完整无撕裂。如果换成TCP协议,偶尔会出现一帧被拆成多段到达的情况,但组帧逻辑能正常处理,画面不会花。
如果距离超过15米或者中间有金属车架遮挡,WiFi信号会衰减,此时帧率会掉到6~8帧/秒,偶尔还会出现连续几帧丢失。我的建议是:调车时把电脑放在赛道附近,手机热点也放近一点,ESP8266的天线尽量避开碳纤维车架。
7.2 常见问题一:为什么单片机端发了一帧就卡死
这个坑我印象太深了。第一次接通时,图像只显示了一帧,然后主控板就直接死机。排查了半天,最后发现是send_buf的长度定义不够:我把数组定义成了uint8 send_buf[4 + SEND_W * SEND_H],但实际帧尾还有一个校验和,数组越界了。
数组越界在单片机上是玄学问题,有时候不报错,只在特定数据量下把其他变量踩坏。解决办法是把发送缓冲大小留足,甚至在末尾多留几个字节:
static uint8 send_buf[4 + SEND_W * SEND_H + 8];另外,还有可能是串口发送函数自身的问题。有些逐飞SDK的uart_putbuff接口如果传入的长度是0,或者被中断打断,会卡在等待状态。遇到这种情况,可以在发送前关一下中断,发完再开,但这个需要谨慎,影响实时性。
7.3 常见问题二:为什么ESP8266连不上热点
我总结了几个高频原因:
- 电脑和ESP8266不在同一个网段。比如电脑连的是路由器,但ESP8266连的是手机热点,那肯定连不上TCP;
- 热点频率是5GHz,ESP8266不支持。手机热点要手动设置成2.4GHz优先;
- 热点开启了AP隔离或客户端隔离,设备之间无法互访。有些公共WiFi就这样,换个路由器热点就好;
- TCP端口被防火墙拦截。先临时关闭防火墙测试,能通再考虑加白名单。
调试时不要凭感觉猜,用串口助手先看ESP8266返回的信息。AT+CWJAP失败会有明确的error code,连接TCP失败也会有提示,一步一步排查其实很快。
7.4 常见问题三:图像花屏、缺行、马赛克
这类问题绝大多数不是WiFi丢包,而是主控板端发送的数据本身就不连续。
我遇到过一次“每隔几行就花一次屏”的情况,排查后发现是串口发送用了两个函数调用:先发帧头和数据的一部分,再发剩余部分。中间被摄像头DMA中断打断,两个串口发送操作中间插入了一大段其他数据,接收端就乱了。
解决办法是把整帧所有数据一次性放到一个连续内存里,然后一次性调用批量发送接口,不要在发送过程中被其他任务打断。如果必须分段发送,就在发送前enter_critical禁止中断,发完再退出。
还有一个可能:电源波动导致ESP8266内部丢数据。这种情况通常表现为“WiFi连接正常,但电脑端偶尔收到乱码”。解决方法是给ESP8266单独供电,并加一个大电容。
7.5 几个提高传输效率的小技巧
等整套图传稳定之后,你肯定不满足于10帧,这里分享几个我实际验证过的小技巧:
- 把灰度图变成二值图再发送。智能车图像处理里,二值化后的图像才是算法真正看的东西,直接发送二值图不仅帧率翻倍,而且调试时能看到阈值调节的真实效果;
- 用DMA发送。逐飞SDK支持串口DMA发送,把发送缓冲地址交给DMA后,CPU可以同时去跑图像处理,帧率能再提升30%左右;
- 局部裁剪。很多时候你只关心赛道前方60行,那就只发图像顶部120行里的60行,数据量直接减半;
- 把ESP8266的TCP连接拆成UDP。UDP少了ACK确认和重传,延迟更低,但会有丢包风险,需要在组帧协议上加更多容错。
8. 写在最后:给新手的建议
整套移植做下来,老实说难度并不高,真正的门槛在于你愿不愿意一步一步把串口、WiFi、TCP、组帧这四个环节单独调通再合到一起。我见过太多同学一上来就把所有代码堆在一起,结果出了问题根本不知道是摄像头没出图、串口没发送、还是ESP8266没连上。
我的建议是严格按这个顺序走:先在串口助手里调通ESP8266的AT指令,再用电脑串口助手直接看主控板发的帧头能不能收到,最后才把ESP8266串进去。每一步都验证通过,再合起来跑,整个过程会顺利很多。个人体会是,WiFi图传这东西,一旦跑通了一次,后面换主控板、换摄像头、换压缩方案都非常快,因为核心的移植思路是一致的:先搞定摄像头出图,再搞定数据传输,最后搞定PC端显示。你掌握的不只是“给逐飞主控板加图传”,而是一套可以复用的无线图像调试方案。