用20块的摄像头(不带fifo的OV7670)做WiFi实时传图小车
零、导读
在这篇文章中,你将看到如下内容:
- OV7670 QVGA配置的注意事项;
- 将FPGA做成一个FIFO驱动不带FIFO的OV7670;
- libjpeg阉割库的使用;
- ESP8266传图给上位机的简单实现。
- ESP8266传图给OneNET,微信小程序的方案
- 项目代码可以参考https://github.com/InfiniteZh/ov7670-
一、项目背景与吐槽
该项目是大三下的嵌入式短学期,因为一些的原因做的不太一样,正常做的是一个STM32F103的WiFi上位机的项目,但为了玩地快乐我做了一个FPGA控制的小车,并且还兼备了WIFI传图,上位机/微信小程序显示与控制地功能。
我在老师发布项目之后的三天内,使用STM32F1战舰开发板进行了测试(毕竟一下子玩的太大,怕失手),确定了初步方案,当时感觉是捡了一个大便宜,做了一个简单的任务,但当我看到FPGA的RTL图的时候,我自闭了。
要往上面这个FPGA中动手脚是比较困难的。特别是在我对整个sdram存储的运用还不太熟练的情况下。
吐槽:本来没打算做微信小程序的,结果在最后的实验验收要求上看到了安卓手机控制。阿这,这是我没想到的,只得硬着头皮做完。。。
二、项目设计方案
回归正题,这次的任务的要求如下:
基于FPGA小车、STM32最小系统、摄像头、无线模块等常用电子模块,实现一个远程可视的遥控小车。
对此我的设计方案如下:
系统设计方案
由系统框图所示,整个系统设计方案分成三个部分,分别是FPGA部分,STM32部分以及上位机/云端部分。
1、FPGA部分
使用黑金的AX301来进行设计,FPGA在系统中的作用是获取图像、存储图像、传输图像以及控制小车。
- 获取图像
- 使用OV7670摄像头来进行图像获取,其中对摄像头的配置,采用QVGA、RGB565的格式输出,输出窗口设置为256*128的大小
- 图像存储
- 使用SRAM来进行图像的存储,同时采用乒乓操作,一边获取图像一边传输图像;
- 图像的传输
- 为了加快图像的传输速率,采用流水线,将RGB565的图像转换成灰度图,然后通过SPI作为从机进行图像的传输;
- 小车控制
- 根据获得的控制指令,输出1KHZ的PWM波驱动电桥控制电机从而控制小车,其中PWM的占空比可调,在进行转弯时为了增加驱动能力,适当提高PWM的占空比。
2、STM32部分
使用自制的STM32F407最小系统板,STM32在系统中的作用是与FPGA进行交互,对图像数据进行处理,与上位机/云端进行交互,是连接云端与底层的通道。
- 与FPGA进行交互
- STM32使用硬件SPI作为主机,使用21M的速率与FPGA进行双向传输,即获取FPGA中存储的图像数据的同时,传达小车的方向控制指令。
- 对图像数据进行处理
- 根据计算,256*128的图像即使是作为灰度图仍然后32KB的大小,相对于ESP8266的115200的波特率来说图像还是太大,因此需要对图像进行jpeg压缩,在jpeg的压缩方面,借助了libjpeg的库。实测灰度图压缩为jpeg后图像大小压缩至1KB左右。大大加快了传输的速率。
- 与上位机/云端进行交互
- STM32驱动ESP8266一方面可以与PC的上位机进行交互,传输图片到上位机上并获取来自上位机的小车控制指令,另一方面可以与OneNET进行交互,通过http协议post图像数据到OneNET上,并能通过get获取OneNET上的小车控制指令。
3、上位机/云端部分
- 这一部分是最后的显示与控制终端,其中上位机部分采用C#进行编写,云端部分借助OneNET这个物联网开发平台存储数据,并通过微信小程序获得图像数据以及传达控制指令。
三、项目具体实现与难点攻克
1、有关FPGA的方案设计
借助黑金的OV7670_sdram的例程,进行修改。
1、OV7670 QVGA的配置
根据OV7670的datasheet,就会发现OV7670有上百个寄存器(阿这),一一地去配置显然过于复杂,不过我使用的程序是黑金官方的一个例程,黑金官方已经给了OV7670的寄存器的配置,而我们要做的是修改他的寄存器配置,因为一般来说FPGA驱动OV7670都是为了VGA的传输,所以基本都是配置了VGA的模式,但VGA模式对于这个项目来说是在是太大了,我们需要将他修改到QVGA模式,并且设置输出的窗口,这里借鉴了STM32驱动OV7670时的配置(详细可以参考这个),以下的代码给出了QVGA的输出以及设置输出窗口的相应寄存器的配置。
这里我给出了三个输出窗口的配置,分别是12864、320240和256*128
但需要注意的时,将OV7670配置成QVGA时,根据datasheet给出的波形图。
我们可以看到他的输出速率是VGA模式的一半,表现在应用中,我发线他的PCLK输出变为原来的一半,即原来我是25M的PCLK,但我设置位QVGA时,他的输出变成了12.5M。
这里必须安利一波singalTap,我本来没有注意这一点,然后他的输出数据一支不对,然后我就将OV7670的全部引脚全部放到singalTap上,然后就发现了他的PCLK变为原来的一半,这时候我才想到datasheet这个时序图的含义……他的这个时钟是会影响到后续的fifo等一系列操作。
2、修改保存的图像的大小
要改变图像大小,只需修改sdram_vga_top.v的最大地址大小
NOTE:这里的地址大小最好是与wr_length和rd_length的整数倍,否则在显示图像的时候会出现图像会进行移动。
注意:我在上面记录了OV7670的配置时给了320*240的图像大小,但我最后并没有使用,一方面是因为图像大了传输过慢,另一方面是因为建议图像的大小是他的wr_length以及rd_length的整数倍,否则显示图像的时候可能会出现图像的移动。
3、FPGA与STM32的SPI通信
为了实现高速率的传输,我设置了SPI通信为21M,需要注意的是一下几点:
- 为了保证SPI和STM32的通信,一定要让STM32与FPGA共地!!!
- SPI模块的时钟
- 在FPGA这里经过测试发现SPI模块的时钟建议倍频到200M,一开始我设置了100M的时钟,发现SPI通信会发生错位,有时候还会紊乱,提高时钟的频率可以解决这个问题。
- SPI模块的CS
- 一般SPI模块都会设计一个CS,然后又由于STM32的硬件SPI只有3个输出脚,我自己定义了一个CS,但实际的使用效果不佳,我查看SingalTap的时候发现,好几次SPI传输的时候这个CS脚会突然来一个高电平,导致我的数据出现问题,预计原因是STM32与FPGA之间的地还是不稳,因此我最后没有用32控制CS,而是引出来进行手动复位……
4、将FPGA做成一个FIFO
因为原来的程序中,摄像头的数据是存储在sdram中,使用了乒乓操作,一边读取一边存储,并且设计是考虑到VGA的时序,基本读取与存储是可以很好的同步的进行,但在我这个项目中,我对图像数据的处理明显时远远慢于OV7670图像数据的输出,因此我需要将FPGA做成一个保存数据的FIFO(吐槽一下:此处点题,20块的摄像头不带fifo就是这么真实,但凡他有一个fifo我可以简便不少…..)
为了达到fifo的效果,我从sdban_switch入手修改了他的交换bank的条件
1 | case(state_write) |
如上所示,在读取完并且写入完毕后才交换bank,以此来配合较慢的读取,但是相应的最终会导致显示的图像有延迟,延迟取决于传输时间、压缩时间等一系列因素。
不过,其中的神来之笔是我将读取的时钟更换了,不再由pll锁相环提供,而是由SPI模块的读取完毕的信号来作为时钟,通过rtl图可以清楚的看到这一点
这条时钟一方面是作为sdram读取fifo的时钟,另一方面是作为RGB565转换灰度图的时钟,因为这个转换是完全流水线结构,所以他的只需要经过3个时钟周期的时滞就可以8位流量的输出灰度值。
5、小车的驱动
小车的驱动主要是依靠PWM来进行驱动,但需要注意的是,转弯时的摩擦力比较大,需要更强的驱动能力,因此在收到转弯指令的时候可以适当提高PWM的占空比,提高驱动能力。
2、有关STM32的设计
1、libjpeg库的使用
这个库是我在CSDN找的,这是链接https://blog.csdn.net/forwardjia/article/details/82179191
但他是RGB的压缩,我为了更进一步的压缩,把他修改成了灰度图的jpeg压缩,具体可以看我的代码,或者直接使用。
这位大佬移植了libjpeg,不过貌似是一个比较旧的版本……压缩质量使用的还是浮点数,不过不影响使用。
值得一提的是,如果去查看STM32Cube_FW_F4可以在其的Projects->STM32F429-Discovery->Application下可以看到LibJPEG,这个是官方的移植,不过他把整个LibJPEG给移植过来了,整个工程太大了,所以还是选择使用这个阉割版。。。。。。
2、STM32与上位机通信
在这里我设置了ESP8266作Client,上位机作为Server,ESP8266通过配置连接WIFI,然后访问我电脑的IP,就可以与上位机建立socket进行图片的传输。
STM32与上位机机通信最好是要有自定的协议,要有帧头,校验位等,但这个数据量实在有点大,我就只设置了一个传输数据包大小的帧头,然后就粗暴的上传了,不过效果还蛮好的说
效果如下:
3、STM32与OneNET进行交互
STM32对OneNET进行交互,OneNET官方给了许多的协议,例如MQTT,EDP和HTTP等,不过对于我来说,最快的还是直接使用API,用get获取数据点,用post上传图片。
为了顺利的与OneNET进行交互,建议先使用网络调试助手进行调试,调试方面可以参考这个链接:https://blog.csdn.net/qq_42401265/article/details/101053604
使用OneNet提供的服务器IP地址:183.230.40.34 和端口:80 连接服务器。
网络调试助手 使用正点原子的XNet。
对于图像传输,这是我上传的数据
1 | POST http://api.heclouds.com/bindata?device_id=607169499&datastream_id=image HTTP/1.1 |
后面加上图像数据,图像数据可以是用上面链接给的一个小软件。
在传输的时候需要注意的是,在传输末尾要加上两个换行符,这个很关键,没有这两个换行符数据上传就会有很多的问题。另外OneNET的Api-key建议直接使用Master-api-key
在STM32的代码上,如下所示
1 | u8 *p; |
同理,获取OneNET的数据点
1 | USART3_RX_STA=0; |
这里对于收到的数据可以使用cJSON进行解析,但需要注意的是,收到的数据还有一个HTTP的头,需要将他除去,如下所示
1 | HTTP/1.1 200 OK |
因此为了方便使用,我直接使用了strstr函数,通过查找current_value来获取数据点的数据。
4、上位机的编写
上位机的编写我是用的是C#来进行编写,下列展示一下主要的代码
1 | void TSReceive() |
上述代码是TCP Server的线程,在该线程中,通过监听socket,获取数据然后将他显示到Image控件上。
在控制指令方面,我设置了一个帧头,byte[] CtrBuf = new byte[2] { 0xA3, 0x20 };
虽然实际上并没有起到什么作用…….
5、微信小程序的编写
微信小程序方面,下图展示的获取图像数据并展示的代码。
图像的转换是主要靠OneNET获取文件的api,获取图像的二进制流文件,然后将其转换成base64的类型,就可以显示图像了,这里主要要注意两点:
- 在request的函数中加一个responseType: ‘arraybuffer’, 这样接收到的数据就是arraybuffer的形式,就可通过arrayBufferToBase64转换成base64的形式,然后加上 data:image/jpg;base64, 就可以直接显示图像了。
- 使用OneNET获取文件的api的时候要使用master-api-key,否则可能会报错,我当时报的错是 errno:3 auth failed, 为此我还问了OneNET客服呢。。。。。
四、成果展示
1、上位机接受图像
2、手机端微信小程序
五、缺陷与提高部分
本次实验最大的缺陷是延迟过高,一方面是因为受限于Jpeg压缩的效率太低,另一方面则是因为数据量太大,传输的速率太慢,在这里我有一些改进与提高的想法,但因为时间的限制就没有付诸于实践。
1、SPI传输在STM32那边可以使用DMA进行传输,将数据直接存储到定义的存储空间中,但这会引起另一个问题,就是DMA对于SPI双向传输该如何进行处理。毕竟不仅要接收数据,还需要传送方向指令。
2、DMA传输还可以用在串口wifi上传图像上,但相对于第一条,这一条的实现难度可能更大一些。
3、氪金,没错,花钱换一个摄像头,换一个能直接jpeg形式输出的摄像头,例如ov2640,这样就可以省去jpeg压缩的时间,大大提高效率,(但凡有个好的摄像头,这个东西也不至于这么复杂)
4、增加协议。包括STM32与FPGA通过SPI传输的协议,以及STM32与上位机的传输协议。如果是用http传输,STM32与OneNET的协议倒不是非常必要。STM32与FPGA通过SPI的协议可以保证数据传输的稳定与可靠,如果可以最好与CS脚联合设计。STM32与上位机的协议也可以保证出错的概率降低。(目前虽然差错出现的概率不大,但还是会有。。。。。)
六、评价
总的来说,这一个项目虽然不大,但他的涉及面广,从FPGA到STM32再到上位机和云端小程序等,(我就是伪全栈工程师)。大学在我看来还是要什么都去碰一碰,毕竟学习的道路是越来越专精的,在高考的时候已经从众多行业中选择了电子,到了后面的研究生阶段还将要继续细分,因此什么都玩一玩在之后的学习上还是能给我不少帮助的。