博客地址 用20块的摄像头(不带fifo的OV7670)做WiFi实时传图小车 – Infinite’s Blog
在这篇文章中,你将看到如下内容:
该项目是大三下的嵌入式短学期,因为一些的原因做的不太一样,正常做的是一个STM32F103的WiFi上位机的项目,但为了玩地快乐我做了一个FPGA控制的小车,并且还兼备了WIFI传图,上位机/微信小程序显示与控制地功能。
我在老师发布项目之后的三天内,使用STM32F1战舰开发板进行了测试(毕竟一下子玩的太大,怕失手),确定了初步方案,当时感觉是捡了一个大便宜,做了一个简单的任务,但当我看到FPGA的RTL图的时候,我自闭了。
编辑
还有许多小模块没全部展现出来,特别是在sdram ip中,害,自闭
要往上面这个FPGA中动手脚是比较困难的。特别是在我对整个sdram存储的运用还不太熟练的情况下。
吐槽:本来没打算做微信小程序的,结果在最后的实验验收要求上看到了安卓手机控制。阿这,这是我没想到的,只得硬着头皮做完。。。
回归正题,这次的任务的要求如下:
基于FPGA小车、STM32最小系统、摄像头、无线模块等常用电子模块,实现一个远程可视的遥控小车。
对此我的设计方案如下:
编辑
系统框图
系统设计方案
由系统框图所示,整个系统设计方案分成三个部分,分别是FPGA部分,STM32部分以及上位机/云端部分。
使用黑金的AX301来进行设计,FPGA在系统中的作用是获取图像、存储图像、传输图像以及控制小车。
使用自制的STM32F407最小系统板,STM32在系统中的作用是与FPGA进行交互,对图像数据进行处理,与上位机/云端进行交互,是连接云端与底层的通道。
借助黑金的OV7670_sdram的例程,进行修改。
1、OV7670 QVGA的配置
根据OV7670的datasheet,就会发现OV7670有上百个寄存器(阿这),一一地去配置显然过于复杂,不过我使用的程序是黑金官方的一个例程,黑金官方已经给了OV7670的寄存器的配置,而我们要做的是修改他的寄存器配置,因为一般来说FPGA驱动OV7670都是为了VGA的传输,所以基本都是配置了VGA的模式,但VGA模式对于这个项目来说是在是太大了,我们需要将他修改到QVGA模式,并且设置输出的窗口,这里借鉴了STM32驱动OV7670时的配置(详细可以参考这个ov7670摄像头分辨率设置方法详解 - STM32/8),以下的代码给出了QVGA的输出以及设置输出窗口的相应寄存器的配置。
这里我给出了三个输出窗口的配置,分别是128*64、320*240和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,需要注意的是一下几点:
4、将FPGA做成一个FIFO
因为原来的程序中,摄像头的数据是存储在sdram中,使用了乒乓操作,一边读取一边存储,并且设计是考虑到VGA的时序,基本读取与存储是可以很好的同步的进行,但在我这个项目中,我对图像数据的处理明显时远远慢于OV7670图像数据的输出,因此我需要将FPGA做成一个保存数据的FIFO(吐槽一下:此处点题,20块的摄像头不带fifo就是这么真实,但凡他有一个fifo我可以简便不少.....)
为了达到fifo的效果,我从sdban_switch入手修改了他的交换bank的条件
case(state_write)
3'd0:begin
if(frame_write_done && frame_read_done) //to be sure data with the same image has been wrote
state_write <= 3'd1;
else
state_write <= 3'd0;
end
3'd1:begin wr_load <= 1'b0; state_write <= 3'd2; end
3'd2:begin wr_load <= 1'b1; state_write <= 3'd3; end
3'd3:begin wr_load <= 1'b0; state_write <= 3'd4; end
3'd4:begin
if(bank_switch_flag)
state_write <= 3'd5;
else
state_write <= 3'd4;
end
3'd5:begin
if(frame_write_done && frame_read_done) //to be sure data with the same image has been wrote
begin
wr_bank <= ~wr_bank;
state_write <= 3'd0;
end
else
begin
wr_bank <= wr_bank;
state_write <= 3'd4;
end
end
default:;
如上所示,在读取完并且写入完毕后才交换bank,以此来配合较慢的读取,但是相应的最终会导致显示的图像有延迟,延迟取决于传输时间、压缩时间等一系列因素。
不过,其中的神来之笔是我将读取的时钟更换了,不再由pll锁相环提供,而是由SPI模块的读取完毕的信号来作为时钟,通过rtl图可以清楚的看到这一点
这条时钟一方面是作为sdram读取fifo的时钟,另一方面是作为RGB565转换灰度图的时钟,因为这个转换是完全流水线结构,所以他的只需要经过3个时钟周期的时滞就可以8位流量的输出灰度值。
5、小车的驱动
小车的驱动主要是依靠PWM来进行驱动,但需要注意的是,转弯时的摩擦力比较大,需要更强的驱动能力,因此在收到转弯指令的时候可以适当提高PWM的占空比,提高驱动能力。
1、libjpeg库的使用
这个库是我在CSDN找的,这是链接stm32驱动ov7670 数据转BMP格式再转JPEG存储_木木so的博客-CSDN博客
但他是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进行交互,建议先使用网络调试助手进行调试,调试方面可以参考这个链接:利用串口网络助手上传数据、图片到OneNet平台以及获取数据(HTTP)_川哥子的博客-CSDN博客_onenet上传图片
对于图像传输,这是我上传的数据
POST http://api.heclouds.com/bindata?device_id=607169499&datastream_id=image HTTP/1.1
api-key:your api-key
Host:api.heclouds.com
Content-Length:3493
后面加上图像数据,图像数据可以是用上面链接给的一个小软件。
在传输的时候需要注意的是,在传输末尾要加上两个换行符,这个很关键,没有这两个换行符数据上传就会有很多的问题。另外OneNET的Api-key建议直接使用Master-api-key
在STM32的代码上,如下所示
u8 *p;
int t =0;
p=mymalloc(SRAMIN,32); //申请32字节内存
sprintf((char*)p,"Content-Length:%d\r\n\r\n",pt_buf);
u3_printf("POST http://api.heclouds.com/bindata?device_id=607169499&datastream_id=image&desc=testfile HTTP/1.1\r\n");
u3_printf("api-key: your api key\r\n");
u3_printf("Host:api.heclouds.com\r\n");
u3_printf(p);
for(t = 0;t
同理,获取OneNET的数据点
USART3_RX_STA=0;
u3_printf("GET http://api.heclouds.com/devices/607169499/datastreams/direction HTTP/1.1\r\n");
u3_printf("api-key:your api key\r\n");
u3_printf("Host:api.heclouds.com\r\n\r\n");
delay_ms(100);
USART3_RX_STA=0;
char *c1=strstr(USART3_RX_BUF, "current_value");
return *(c1+16);
这里对于收到的数据可以使用cJSON进行解析,但需要注意的是,收到的数据还有一个HTTP的头,需要将他除去,如下所示
HTTP/1.1 200 OK
Date: Wed, 01 Jul 2020 15:23:14 GMT
Content-Type: application/json
Content-Length: 133
Connection: keep-alive
Server: Apache-Coyote/1.1
Pragma: no-cache
因此为了方便使用,我直接使用了strstr函数,通过查找current_value来获取数据点的数据。
4、上位机的编写
上位机的编写我是用的是C#来进行编写,下列展示一下主要的代码
void TSReceive()
{
aimSocket = mySocket.Accept();//服务端监听到的Socket为服务端发送数据的目标socket
infoLabel.Text = "连接成功";
byte[] buffer = new byte[4];
while (true)
{
try
{
aimSocket.Receive(buffer, buffer.Length, SocketFlags.None);
int contentLen = BitConverter.ToInt32(buffer, 0);
int size = 0;
MemoryStream ms = new MemoryStream();
while (size < contentLen)
{
//分多次接收,每次接收256个字节,
byte[] bits = new byte[256];
int r = aimSocket.Receive(bits, bits.Length, SocketFlags.None);//接收到监听的Socket的数据
if (r == 0)
{
MessageBox.Show("连接断开");
break;
}
ms.Write(bits, 0, r);
size += r;
}
Image img = Image.FromStream(ms);
picBox.Image = null;
picBox.Image = img;
}
catch
{ }
}
}
上述代码是TCP Server的线程,在该线程中,通过监听socket,获取数据然后将他显示到Image控件上。
整体上位机布局
在控制指令方面,我设置了一个帧头,byte[] CtrBuf = new byte[2] { 0xA3, 0x20 };
虽然实际上并没有起到什么作用.......
5、微信小程序的编写
微信小程序方面,下图展示的获取图像数据并展示的代码。
图像的转换是主要靠OneNET获取文件的api,获取图像的二进制流文件,然后将其转换成base64的类型,就可以显示图像了,这里主要要注意两点:
界面做的简陋了一点。。。。。
本次实验最大的缺陷是延迟过高,一方面是因为受限于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再到上位机和云端小程序等,(我就是伪全栈工程师)。大学在我看来还是要什么都去碰一碰,毕竟学习的道路是越来越专精的,在高考的时候已经从众多行业中选择了电子,到了后面的研究生阶段还将要继续细分,因此什么都玩一玩在之后的学习上还是能给我不少帮助的。