十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F407+OV2640裸机网络摄像头:LWIP UDP传输JPEG帧实战

STM32F407+OV2640裸机网络摄像头:LWIP UDP传输JPEG帧实战 简介基于 STM32F407 微控制器、OV2640 摄像头模块、数字摄像头接口 DCMI 和静态随机存储器 SRAM并通过轻量级 TCP/IP 协议栈 LWIP 实现网络图像传输的嵌入式工程源码适用于熟悉 STM32 底层开发与网络协议栈的工程师也可作为工业视觉、远程监控、智能图像处理等项目的参考方案。工程完整演示了摄像头图像采集、SRAM 数据缓存、DCMI 接口读取、以及经过 LWIP 协议栈打包上传至网络的实现流程支持最大分辨率一千六百万像素1600*1200当前在该分辨率下约每秒一至两帧适合分析带宽和处理性能的优化空间。压缩包包含两千个文件总体积约十二兆其中 C 源码与 H 头文件约五百余个构成 STM32 固件主体另外大量 HTML、JavaScript、CSS 文件多为工程说明文档、上位机网页或调试界面TXT 与 PDF 则提供辅助说明整体结构比较清晰。该分享已有约一百五十五人学习源码中包含寄存器级配置、DCMI 数据流读取、SRAM 缓冲管理、LWIP 网卡适配与通信等关键实现能帮助开发者快速理解并复用 MCU 加摄像头的联网方案。1. 从 OV2640 到 LWIP一帧图像怎么从摄像头走到网线先给一个反直觉的结论STM32F407 自带 128KB 主 SRAM 加 64KB CCM总共 192KB却连一张 640×480 的 RGB565 原图614KB都装不下。而这套组合要干的事恰恰是把 OV2640 的数据经 DCMI 接口收进来放进外部 SRAM再交给 LWIP 发到网线上。所以整套方案的难点不在 Cortex-M4 的主频而在三段链路的错峰JPEG 压缩让帧体积从 600KB 量级掉到几十 KB外部 SRAM 给 DCMI DMA 提供乒乓缓冲区LWIP 的内存池在裸机环境下负责把这几十 KB 切成包发出去。适合三类人做图像采集网关、要给设备加网络摄像头功能的工程师以及把“STM32F407LWIPOV2640”当毕设题目的学生。下面按数据流方向拆开讲从 SCCB 配 sensor到 DCMI 同步时序再到 FMC 挂 SRAM 和裸机 LWIP 发包最后收在几个最容易翻车的边界条件上。2. 采集侧数据通道OV2640 的 JPEG 输出、DCMI 同步与 SRAM 帧存2.1 先用 SCCB 让 OV2640 切到 JPEG 模式OV2640 上电默认输出的是 RGB/YUV 原始像素走 8 位并行口直接喂给 DCMI 的话VGA 一帧就是 600KB 以上。192KB 片内内存加上 1MB 外部 SRAM 也经不起这么造更别说 100Mbps 网线每秒只能吞 10MB 级别。所以第一步是让 sensor 输出 JPEG 压缩流把帧体积压到 1060KB网络才发得动。OV2640 的寄存器配置走 SCCB 总线电气上完全可以拿 STM32 的硬件 I2C1 去驱动但实践里模拟 I2C 更常见因为 SCCB 没有 I2C 的复杂时序状态机两个 GPIO 用延时翻转就能稳定跑 100kHz 左右。“stm32f407模拟i2c”这个写法在各大板卡例程里出现的频率远高于硬件 IIC原因就是 SCCB 的停止条件、应答时序都更宽松模拟实现反而少踩硬件 I2C 的忙检测坑。SCCB 和 I2C 有个关键差别每次写寄存器都是一个独立的 start/stop 周期不支持连续写多个寄存器。static void ov2640_wr_reg(uint8_t reg, uint8_t val) { i2c_start(); i2c_write_byte(0x60); /* OV2640 器件地址7 位地址 0x30 左移一位 */ i2c_wait_ack(); i2c_write_byte(reg); /* 寄存器号0x00 ~ 0xFF */ i2c_wait_ack(); i2c_write_byte(val); /* 寄存器值 */ i2c_wait_ack(); i2c_stop(); } static void ov2640_setup(void) { ov2640_wr_reg(0xFF, 0x01); /* 页面切换到 sensor 组 */ ov2640_wr_reg(0x11, 0x01); /* CLKRC内部分频关系 PCLK 上限 */ ov2640_wr_reg(0x12, 0x40); /* COM7分辨率与输出方向的高位控制 */ /* …… 中间是模块自带的 JPEG 参数表条目格式都是 {reg, val} …… */ ov2640_wr_reg(0xFF, 0x00); /* 切回 DSP 组继续写压缩质量相关寄存器 */ }参数说明0xFF 是页面切换寄存器OV2640 内部把寄存器分成 sensor 和 DSP 两组任何一条驱动代码都必须先确认当前在哪一页这也是新手改例程时最常见的“改了没反应”原因。0x11CLKRC决定输入时钟分频直接影响 PCLK 和帧率0x12COM7的低位是分辨率档位。完整的OV2640_JPEG表通常上百条直接采用模块例程或原厂 demo 里那一份只把 I2C 读写底座换成自己的即可不建议自己逐个推导值。验证方法很简单初始化完成后读帧缓存前两个字节如果是FF D8说明 JPEG 模式已经生效。提示模拟 I2C 的两个 GPIO 要避开 DCMI 的 D0~D7、PCLK、HSYNC、VSYNC 引脚否则初始化 sensor 的瞬间 DCMI 会收到一串毛刺帧后续的“丢首帧”逻辑反而更难判断。2.2 DCMI 的极性配置四组组合里只有一组不出花屏DCMI 是 STM32F407 专门给并行摄像头留的接口8 位模式下信号有 D7~D0、PIXCLK、HSYNC、VSYNC。像素时钟边沿、行同步和场同步的有效电平分别由 DCMI 控制寄存器里的 PCPOL、HSPOL、VSPOL 三个位决定。OV2640 模块的典型接法里VSYNC 低有效、PCLK 上升沿采样但不同卖家模块的串阻和布线会引入相移板子一换就可能翻车。PCPOLVSPOL现象处理办法00画面上下错位半帧顶部有杂色带VSPOL 改为 1 再测01画面正常但偶发整帧闪烁VSPOL 改回 0检查 VSYNC 是否真接了10画面呈斜纹或锯齿但颜色正常PCPOL 改回 0采样沿选错11帧率减半或花屏严重同时翻转两位逐项测hdcmi.Init.SynchroMode DCMI_SYNCHRO_HARDWARE; hdcmi.Init.PCKPolarity DCMI_PCKPOLARITY_RISING; hdcmi.Init.VSPolarity DCMI_VSPOLARITY_LOW; hdcmi.Init.HSPolarity DCMI_HSPOLARITY_HIGH; hdcmi.Init.CaptureRate DCMI_CR_ALL_FRAME; hdcmi.Init.ExtendedDataMode DCMI_EXTEND_DATA_8B; HAL_DCMI_Init(hdcmi);参数说明SynchroMode必须选硬件同步而不是内嵌同步。这是个特别容易踩的坑——很多人看到 JPEG 流里有FF D8、FF D9标记就以为可以不用接 HSYNC/VSYNC让 DCMI 走内嵌同步自己去“找头”。但 DCMI 的内嵌同步找的是FF 00 xx这类固定四字节码由 ESCR/ESUR 寄存器定义OV2640 的 JPEG 流里并没有插入这种码所以必须接好两根同步线选硬件同步模式。PCKPolarity选上升沿还是下降沿以 2.1 末尾那步 FFD8 验证为准花屏优先查这张表比查 DMA 配置快得多。2.3 用 FMC 把 SRAM 挂到外部总线帧存怎么划分外部 SRAM 要挂在 FMCFlexible Memory Controller的 NOR/PSRAM Bank1 上片选用 NE1 时首地址是 0x60000000。常见芯片是 IS62WV51216容量 512K×16 即 1MB16 位数据总线访问时序由AddressSetupTime和DataSetupTime两个参数控制单位是 HCLK 周期。主频 168MHz 时一个周期约 6nsIS62WV51216 的读写周期典型值在 10ns 左右所以时序给宽一点不会成为瓶颈反而能避开低温或走线过长导致的偶发读错。FMC_NORSRAM_TimingTypeDef timing {0}; timing.AddressSetupTime 1; /* 地址建立2 个 HCLK ≈ 12ns */ timing.DataSetupTime 4; /* 数据建立5 个 HCLK ≈ 30ns */ timing.AccessMode FMC_ACCESS_MODE_A; sram.Init.NSBank FMC_NORSRAM_BANK1; /* NE1首地址 0x60000000 */ sram.Init.MemoryType FMC_MEMORY_TYPE_SRAM; sram.Init.MemoryDataWidth FMC_NORSRAM_MEM_BUS_WIDTH_16; sram.Init.WriteOperation FMC_WRITE_OPERATION_ENABLE; HAL_SRAM_Init(sram, timing, NULL);参数说明AddressSetupTime和DataSetupTime越宽越稳但会占住 AHB 总线。DCMI DMA 每 4 个像素触发一次外部 SRAM 写入如果 FMC 时序拖得太长DMA 请求会排队高 PCLK 下就直接丢数据。所以这两个参数要在稳定性和带宽之间找平衡1/4 这个组合在绝大多数 168MHz 板子上都能跑再低就建议用示波器量片选和写使能的实际宽度了。内存布局按“乒乓采集 单帧邮箱”的模型划分1MB 空间很宽裕区域地址大小用途Ping 缓冲0x6001000064KBDCMI DMA 目标 APong 缓冲0x6002000064KBDCMI DMA 目标 B帧邮箱0x60030000256KB组装完整 JPEG 帧供 LWIP 读取预留0x60070000576KB多帧队列或未来的 OSD 叠加DMA 目标直接指向外部 SRAM而不是先收进片内 SRAM 再 memcpy 到外部。少一次搬运省下的时间在“stm32f407 dcmi 超高速率”这个搜索词对应的场景里非常可观——外部 SRAM 虽然比片内慢但 16 位总线背靠背读写也有几十 MB/s远高于 OV2640 模块常见的 10~25MHz PCLK。提示16 位 SRAM 的地址线接法要看原理图有的板把 SRAM 的 A0 接到 FMC_A0有的接 FMC_A1。拿到新板子先对 0x60000000 和 0x60000002 两个地址各写一个不同的 16 位字再读回来能区分就说明地址映射没问题别死记地址公式。2.4 DMA 双缓冲帧就绪信号怎么给到主循环DCMI 在 F407 上关联 DMA2 的某个流CubeMX 生成的代码里能看到8 位模式下 DCMI 内部会把每 4 个像素打包成一个 32 位字再触发 DMA 请求所以 DMA 的数据长度必须按字计数缓冲字节数必须是 4 的倍数。采集不能停主循环又要同时处理 LWIP 发包因此这里用 DMA 双缓冲模式一块缓冲在接收另一块由主循环拿去发两个角色靠帧中断里切换。volatile uint8_t *g_frame_ready NULL; void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { /* 帧结束中断里判断 DMA 当前要写哪块另一块就是刚收满的 */ if (LL_DMA_GetCurrentTargetMem(hdcmi-DMA_Handle) LL_DMA_CURRENTTARGETMEM0) g_frame_ready CAM_PONG; /* 下一帧写 pingpong 可读 */ else g_frame_ready CAM_PING; }逻辑说明双缓冲开启后DMA 硬件会在两个内存地址之间自动切换GetCurrentTargetMem读出的是“当前正在写入”的目标。帧中断触发时另一块必然是完整的一帧把它的地址赋给g_frame_ready就完成了“采集→主循环”的交接。双缓冲的使能在 HAL 里对应HAL_DMAEx_ConfigDoubleBuffer也可以直接操作 DMA2 流控制寄存器的 DBM 位使用LL_DMA_EnableDoubleBuffer。这里只写交接逻辑是因为改流配置属于 CubeMX 生成代码的范畴不同固件版本生成的初始化顺序不一样直接改寄存器最容易和自动生成代码打架。这个方案里没有互斥锁因为裸机下只有主循环一个消费者帧中断只负责“置一个指针”不会有并发写。代价是如果主循环处理太慢新帧会直接覆盖旧缓冲所以丢帧策略必须在主循环侧处理而不是中断里。3. 裸机 LWIP 网络出口NO_SYS 下的缓冲规划与 JPEG 分包3.1 为什么裸机 LWIP 的常见模型是 UDP很多搜“lwip 裸机移植模型”的人最终落到两个选择上NO_SYS1 用 RAW API或 NO_SYS0 配一个精简 sys 层跑 netconn。前者是干净利落的裸机方案RAW API 不需要邮箱、信号量中断处理和主循环轮询天然串行后者要补 sys 层的大量桩函数移植成本高收益只是换来类 BSD socket 的调用风格对一帧几十 KB 的 JPEG 传输并不划算。传输层选 UDP 还是 TCP取决于任务需要“实时预览”还是“可靠单帧”。UDP 无 ACK、无窗口、无重传发出去就不管帧率稳定TCP 有窗口控制和 ACK 节流报文要等确认才能释放 pbuf在裸机单线程里一旦 ACK 延迟下一个窗口就卡住。图像采集对丢包容忍度高下一帧马上就来所以实时预览型项目普遍用 UDP。TCP 适合“这张图必须完整到达”的抓拍场景但要在 tcp_sent 回调里维护一个发送队列代码量直接翻倍。顺带说一句裸机 F407 上给 LWIP 集成 TLS 不现实没有硬件加解密加速握手要占几十 KB 堆内存图像流的重传机制还会拖垮帧实时性。常见替代做法是应用层做轻量加密比如在 UDP 载荷里加一组预共享的混淆字节。3.2 LWIP 内存参数和以太网 DMA 描述符的放置LWIP 在裸机上跑内存规划比栈配置更影响稳定性。JPEG 帧要先从外部 SRAM 拷进 pbuf才能交给以太网 DMA 发出去所以 pbuf 池的大小直接决定一帧最多拆多少片。ETH 外设的 DMA 描述符和收发缓冲区必须在片内 SRAM1 里不能放 0x10000000 开头的 CCM RAM——ETH、DCMI、DMA1/2 全都访问不到 CCM。参数推荐值说明MEM_SIZE24KBLWIP 堆大小TCP 和 PBUF_RAM 都从这里切MEMP_NUM_PBUF16裸机下够用太小会偶发 ERR_MEMPBUF_POOL_SIZE16静态 pbuf 池数量接收路径主要消耗它PBUF_POOL_BUFSIZE1536对齐到 4 字节必须能装下 1518 字节以太网帧TCP_MSS14601500 减 IP/UDP 头TCP 用TCP_WND8 * TCP_MSS局域网下这个窗口就能跑满吞吐参数说明MEM_SIZE和PBUF_POOL_SIZE都吃片内 SRAM调大确实能减少丢包但每加 1KB 都在挤压 DCMI 和 ETH 的描述符空间。我的习惯是先按上表跑通再观察stats里的lwip_stats哪个池子归零就只加哪个。PBUF 池和 JPEG 发送是两条独立路径接收方向的 pbuf 池是给 ARP、ICMP 控制和偶尔的回环用的发送方向上我是直接用PBUF_RAM分配一整段连续内存再 memcpy避免链式 pbuf 的内存碎片问题。以太网 PHY 用 LAN8720A 还是 DP83848对 LWIP 这一层没有任何区别RMII 接口寄存器配置好后协议栈只看到 ETH MAC。真正要注意的是 DMA 描述符数组要放在 0x20000000 到 0x2001BFFF 这段 SRAM1 里别让链接脚本把它排到 SRAM2 或 CCM否则以太网收发会时好时坏而且没有任何报错。3.3 UDP 分包发送一帧 JPEG 的最小代码RAW API 发 UDP 的路径比 netconn 短udp_new建 PCBudp_bind绑本地端口主循环里把一帧按 1400 字节切片每片pbuf_alloc填入数据udp_sendto发出再释放。以太网 MTU 是 1500IP 头 20 字节、UDP 头 8 字节单包最大载荷 1472。留 1400 而不是顶满 1472是为了在首包前面加一个 4 字节的帧长度头时不至于拆成两个包。static struct udp_pcb *g_upcb; void lwip_udp4_start(void) { g_upcb udp_new(); udp_bind(g_upcb, IP_ADDR_ANY, 6666); } err_t send_jpeg_frame(uint8_t *jpeg, uint32_t len) { uint32_t off 0; static ip_addr_t remote IPADDR4_INIT_BYTES(192, 168, 1, 50); /* 接收端 IP */ while (off len) { uint16_t chunk (len - off 1400) ? 1400 : (uint16_t)(len - off); struct pbuf *p pbuf_alloc(PBUF_TRANSPORT, chunk, PBUF_RAM); if (p NULL) return ERR_MEM; memcpy(p-payload, jpeg off, chunk); err_t err udp_sendto(g_upcb, p, remote, 8888); pbuf_free(p); if (err ! ERR_OK) return err; off chunk; } return ERR_OK; }逻辑说明每次循环先算出剩余长度和本片大小pbuf_alloc的第二个参数是载荷长度协议栈会自动在前面补出以太网、IP、UDP 三层头部。udp_sendto是异步的底层把包交给 ETH DMA 描述符后就返回所以pbuf_free在这个位置是安全的。如果ERR_MEM频发说明MEM_SIZE不够或上一帧的 pbuf 还没被 DMA 消费完这时优先检查发送描述符数量而不是盲目加大内存。接收端要能正确组帧必须在第一片里带帧长度。最简单可靠的做法第一片 payload 前 4 字节放len的小端表示接收端收满len字节即为一帧之后遇到的未对齐数据直接丢弃。上位机用 Python 的 socket 收到第一个包后按这个长度循环 recv 就行。3.4 发不过来就丢帧主循环的仲裁策略摄像头采集是硬实时网络发送是尽力而为两者之间必须有一个明确的“谁让谁”的规则。典型策略是“最新帧优先”主循环发现g_frame_ready还没处理完就直接放弃上一帧立即接手新帧。丢弃的判断不放在中断里而是主循环每次循环进来发现g_frame_ready指向的缓冲已经被新帧覆盖了一个字节就知道这帧已经废了连找 FFD9 的功夫都省下直接把指针清空等下一帧。为什么不在中断里做判断因为“发现被覆盖”本身需要读多个字节中断里做这件事会抢占 DCMI 的 DMA 带宽高 PCLK 下反而加剧丢帧。裸机模型下中断只做一件事置指针。仲裁、拷贝、发送全部放主循环。带宽核算也很直观QVGA 分辨率的 JPEG 一帧约 10~20KB按 15fps 算只有 300KB/s 上下100Mbps 网线完全无压力VGA 分辨率 30fps 时JPEG 码率随画面复杂度波动很大运动剧烈画面一帧能到 80KB 以上预算要按静止画面的三倍留。真正卡死帧率的往往不是网络而是 OV2640 的 PCLK 和 DCMI 的搬运能力。4. 源码怎么组织主循环状态机、内存划分与参数表4.1 主循环状态机捕捉帧就绪事件这套源码的组织核心是一个三状态主循环等待帧、解析长度、发送。没有 RTOS状态之间的转换全由g_frame_ready指针驱动。int main(void) { SystemClock_Config(); /* 168MHz */ MX_GPIO_Init(); MX_DCMI_Init(); /* 极性按 2.2 的排查表调整 */ fmc_sram_init(); /* 挂载外部 SRAM */ ov2640_setup(); /* SCCB 配 sensor 到 JPEG 模式 */ dcm_dma_double_buffer_start(); lwip_eth_init(); /* PHY 复位 LWIP 协议栈初始化 */ g_frame_ready NULL; while (1) { if (g_frame_ready ! NULL) { uint32_t len find_jpeg_len(g_frame_ready, MAX_FRAME_SIZE); if (len 4) { send_jpeg_frame(g_frame_ready, len); } g_frame_ready NULL; /* 丢帧策略本帧发完立即释放 */ } sys_check_timeouts(); /* 裸机 LWIP 必须周期性喂定时器 */ } }逻辑说明find_jpeg_len在外部 SRAM 里扫描 FFD8 和 FFD9找到合法的 JPEG 结束标记才返回长度找不到说明这帧被截断直接丢弃。g_frame_ready NULL永远放在发送之后放在发送之前也没问题因为 DCMI 的连续采集会覆盖旧缓冲发一个被覆盖的帧反而是浪费。sys_check_timeouts是裸机 LWIP 的定时器泵负责 ARP 缓存老化、TCP 重传等周期任务主循环每轮调用一次即可不需要精确到毫秒。这里有个性能细节值得留意send_jpeg_frame是从外部 SRAM 读数据再拷进片内 pbuf整个发送过程占主循环。如果一帧 40KB 在网络侧耗时 5ms而 DMA 双缓冲半帧时间只有 6ms那么这个循环已经贴边了。解决办法要么降帧率要么把 JPEG 质量寄存器调低让帧体积掉到 20KB 以下这就是“JPEG 质量换帧率”的具体落点。4.2 关键参数表DCMI、FMC、LWIP 三处联动这三处参数不是独立的。DCMI 的窗口大小决定一帧有多少数据FMC 的时序决定 DMA 写入外部 SRAM 的速度LWIP 的 pbuf 池决定发送能切多碎。任何一处改大都会挤压另外两处的余量。参数位置参数推荐值联动影响DCMI窗口宽高320×240 或 640×480分辨率提高一倍帧数据量翻四倍DCMI扩展数据模式8 位决定 DMA 按字打包FMCAddressSetupTime1太大会拖慢 DMA 写入FMCDataSetupTime4太小偶发读错位LWIPPBUF_POOL_BUFSIZE1536必须大于最大帧长LWIPMEM_SIZE24KB影响 PBUF_RAM 发送路径以太网TX/RX 描述符各 4 个太少会丢发送请求DCMI 的捕获率参数CaptureRate选了DCMI_CR_ALL_FRAME意思是每一帧都产生一次帧中断并写入缓冲。也可以选只采奇数场或偶数场来降帧率但 JPEG 模式下不建议这么干因为 sensor 端并没有真正降低输出帧率DCMI 只是隔帧丢弃浪费的采集带宽还在。4.3 一帧数据的完整搬迁路径与帧率核算把整条链路按字节过一遍OV2640 输出 JPEG 字节流每个字节跟随一个 PCLK 被 DCMI 采样每 4 字节打包成 32 位字经 DMA2 写入外部 SRAM 的 ping/pong 缓冲。主循环发现g_frame_ready后扫描 FFD9 确认帧边界然后循环 memcpy 到 pbuf每个 pbuf 经udp_sendto挂到 ETH DMA 描述符由 MAC 按帧率发出。开销大头有三个memcpy 从外部 SRAM 读、pbuf 分配和释放、ETH DMA 等待上一次发送完成。以 QVGA 15fps、每帧 15KB 为例memcpy 约 0.4msUDP 发送约 2ms剩余时间全在主循环空转帧率轻松达标。VGA 30fps 则要重新核算因为 JPEG 帧体积可能到 60KB 以上单帧发送时间逼近半帧采集时间就会频繁触发丢帧策略。核算公式就一句总耗时 采集时间 memcpy 时间 发包时间采集时间由 PCLK 和帧字节数决定发包时间由网线和udp_sendto的单片开销决定。把这三个数算清楚就能判断瓶颈在 sensor、总线还是网络而不是盲目加内存。5. 几个容易翻车的边界条件极性、CCM 与首帧5.1 极性排查顺序先 PCLK再 VSYNC花屏问题按 2.2 的表格排查时顺序建议固定为先翻 PCPOL 看斜纹是否消失再翻 VSPOL 看上下错位是否解决最后才是 HSPOL。原因是 PCLK 采样沿错了后面所有判断都没意义。手边没有示波器时用一个白墙固定画面来测斜纹和错位会非常明显。/* 快速切换采样沿的两种写法改完立即看效果 */ __HAL_DCMI_DISABLE(hdcmi); hdcmi.Init.PCKPolarity DCMI_PCKPOLARITY_FALLING; HAL_DCMI_Init(hdcmi); __HAL_DCMI_ENABLE(hdcmi);这段代码演示的是“改极性不必重启整个外设”先关 DCMI 再改再开即可。注意HAL_DCMI_Init在使能状态下调用可能触发断言所以要先把外设关闭。5.2 CCM 的 64KB 别放 DMA 缓冲区但有个安全用途CCM RAM 在 0x10000000主核访问它是全速的但 DMA1、DMA2、ETH、DCMI 全都碰不到。链接脚本如果不小心把大数组排到 CCMDCMI DMA 写它的结果是“静默失败”——没有任何错误标志数据就是不动。排查方法定义一个带初值的数组放在 CCMDMA 搬运后对比值没变就说明地址没生效。CCM 的正确用途是放纯 CPU 访问的数据JPEG 初始化表、printf 缓冲、协议解析的临时结构体。LWIP 的发送描述符和 pbuf 池绝不进 CCM这是 F407 上最容易踩的地址陷阱。5.3 首帧脏数据和 FFD9 兜底上电后 sensor 的 PLL 和自动曝光都在收敛期前几帧经常是半黑、花屏或只有上半帧。最省事的办法是在 VSYNC 中断里数帧前三帧直接丢弃第四帧再开放g_frame_ready。DCMI 是连续采集不存在“重新同步”的说法丢帧只是让主循环不认它硬件层面照常走。uint32_t find_jpeg_len(const uint8_t *buf, uint32_t max) { uint32_t end 0; for (uint32_t i 0; i 1 max; i) { if (buf[i] 0xFF buf[i 1] 0xD8) end 0; /* 发现新的 SOI重新累计 */ else if (buf[i] 0xFF buf[i 1] 0xD9) end i 2; /* EOI帧结束 */ } return end; /* 返回 0 表示未找到完整帧 */ }这段扫描有两个值得留意的点一是循环里把最后一个 FFD9 当作帧尾因为双缓冲后半帧往往和下一帧的前半帧粘连取最后一个 EOI 才能去重二是返回值为 0 时主循环直接释放绝不上报“错误帧”状态给上位机。这样即使 sensor 偶发丢行网络侧拿到的永远是完整 JPEG。本文还有配套的精品资源点击获取
返回列表