赞
踩
本文利用STM32F407单片机、OV2640摄像机模块以及ESP8266 WIFI模块,并基于C#编写的TCP上位机服务,来实现图像的无线传输。
本文受启发于博客:ESP8266+STM32F407+OV7670实现图片传输,在此感谢该文作者。与该文不同的是,本文采用的摄像机模块是0V2640,传输的数据是压缩之后的jpeg格式的图像数据,而不是像上文博主那样,将RGB565数据直接传输到上位机。此外,本文存在和上文博主同样的问题,即采用串口传输方式,数据传输速率过低,实际应用中,发现3秒左右才能够传出一帧320*240的图片。本文接下来对整个图传功能中使用到的模块一一进行详细介绍。
在整个图传项目中,使用到的最核心的模块就是OV2640模块, 关于该模块的介绍以及详细的驱动,可以参考文章:STM32F4驱动OV2640摄像头,该文详细介绍了OV2640模块、DMCI接口及整个OV2640的驱动。此处进行简要介绍:
STM32F4驱动OV2640采用的是DCMI接口及DMA直接存储器访问,对于DMA的相关介绍可以参考文章:STM32:DMA。整个驱动流程是:STM32单片机通过DCMI接口,获取OV2640摄像头采集到的图像数据,并通过提前配置好的DMA数据流,将数据传输到LCD或内部数组。如下图所示,是通过DMA配置,将DCMI采集到的图像数据传输到LCD显示屏的效果:

关于LCD显示屏的介绍及详细驱动,可以参考博文: STM32: LCD显示。这样,首先通过OV2640及LCD屏幕,将摄像头模块采集到的数据显示在屏幕上。
该部分的源码: OV2640驱动,虽然我们将OV2640采集到的图像成功显示在了LCD屏幕之上,但是,我们最终的目的是将图像数据利用ESP8266模块通过WIFI传输到上位机或网页中,因此,我们还需要ESP8266模块。
ESP8266是比较常见的WIFI模块,该模块有三种不同的工作模式,即softAP 模式,station 模式,softAP + station 共存模式。(SoftAP:即无线接入点,是一个无线网络的中心节点,通常使用的无线路由器就是一个无线接入点;Station:即无线终端,是一个无线网络的终端端。)

本文我们将ESP8266作为客户端,并将其传输模式设置为透传,让其连接位于电脑的TCP上位机服务器,然后将OV2640采集的图像数据通过单片机传输给ESP8266模块,并采用WIFI发送给上位机。ESP8266的配置过程如下:
此处要注意,本文的ESP8266是通过AT指令进行配置的,因此要保证ESP8266已经烧录了AT固件库,一般网上买的ESP8266模块默认会烧录AT固件库,如若没有,可以自行进行烧录。
基本的模块介绍已经完成,通过模块介绍,我们也可以发现,无线图传模块的工作原理是:首先我们利用STM32和OV2640模块,采集图像数据,然后,我们配置好ESP8266,让其连接上位机服务器,然后,我们通过串口,将STM32采集到的图像数据传输给ESP8266,由于ESP8266配置的是透传模式,因此其会将STM32通过串口发送过来的数据原封不动的通过WIFI发送给上位机,上位机在将这些图像数据解析为图像显示出来就可以了,如下图所示:

上文中,我们介绍OV2640模块时,将图像显示在LCD屏幕上,但是,我们真正的目的,是将数据通过ESP8266模块传输给上位机。其实这两者本质上是一样的,一个是将数据通过DMA传输给LCD屏幕,而另外一个则是将数据传输给串口(因为ESP8266和STM32是通过串口连接的)。
核心传输代码如下:
- //处理JPEG数据
- //当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
- void jpeg_data_process(void)
- {
- if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
- {
- if(jpeg_data_ok==0) //jpeg数据还未采集完?
- {
- DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输
- while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}// 等待DMA2_Stream1可配置
- jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);// 得到此次数据传输的长度
-
- jpeg_data_ok = 1; // 数据已经采集完成,等待被处理
- }
- if(jpeg_data_ok==2) //上一次的jpeg数据已经被处理了
- {
- DMA2_Stream1->NDTR=jpeg_buf_size;
- DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节
- DMA_Cmd(DMA2_Stream1, ENABLE); //重新传输
- jpeg_data_ok=0; //标记数据未采集
- }
- }
- }
-
- //jpeg模式
- void jpeg_test(void)
- {
- u32 i,jpgstart,jpglen;
- u8 headok=0;
- u8 *p;
- u8 effect=0,saturation=2,contrast=2;
- u8 size=3;
- u8 msgbuf[15]; //消息缓存区
-
- //uart4初始化
- LTE_uart3_init(115200);
- LCD_Clear(WHITE);
- POINT_COLOR=RED;
- LCD_DisplayString(30,60,24,"ALIENTEK STM32F4");
- LCD_DisplayString(30,90,24,"OV2640 JPEG Mode");
-
- OV2640_JPEG_Mode(); // JPEG模式
-
- My_DCMI_Init(); //DCMI配置
- DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI配置 输出到数组
- OV2640_OutSize_Set(jpeg_img_size_tbl[size][0], jpeg_img_size_tbl[size][1]);
- DCMI_Start(); //启动传输
- delay_ms(500);
- while(1)
- {
- if(jpeg_data_ok==1) // 已经采集完成一帧图像,开始处理数据
- {
- p=(u8*)jpeg_buf;
- LCD_DisplayString(30,150,24,"Sending JPEG data...");
- jpglen=0; //设置jpg文件大小为0
- headok=0; //清除jpg头标记
- for(i=0; i<jpeg_data_len*4; i++)
- {
- //查找OXFF,OXD8和0XFF,0XD9,获取jpg文件大小
- if((p[i]==0XFF)&&(p[i+1]==0XD8)){
- jpgstart=i;
- headok=1; //标记找到jpg头(FF D8)
- }
- if((p[i]==0XFF)&&(p[i+1]==0XD9)&&headok)//找到头以后,再找FF D9
- {
- jpglen=i-jpgstart+2;
- break;
- }
- }
-
- if(jpglen) // 正常的jpeg数据
- {
- p+=jpgstart;
- for(i=0;i<jpglen;i++) //发送整个jpg文件
- {
- while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); //循环发送,直到发送完毕
- USART_SendData(USART3, p[i]);
- }
-
- }
- jpeg_data_ok = 2; //标记jpeg数据处理完了,可以让DMA去采集下一帧了.
- delay_ms(2000);
- }
- }
- }

上述核心代码其实是两个函数,函数jpeg_data_process()在帧中断中被调用,也就是说,每次采集完一帧图像,该函数都会被调用。在该函数中,主要是修改标志jpeg_data_ok 为1,以便另外一个函数jpeg_test()可以对这一帧数据进行处理,所谓的处理在此处其实就是将数据通过串口发送给ESP8266,ESP8266模块会将数据发送给上位机,并由上位机解析然后显示。
ESP8266在拿到STM32通过串口传输的图像数据之后,会将其发送给上位机,那么ESP8266和上位机之间是如何通讯的呢?是通过TCP/IP协议。
本文使用C#语言,基于TCP/IP协议,写了一个简单的上位机服务,该服务接受ESP8266的连接,并将其发送过来的数据编码为图像进行显示。由于软件写的很简单,因此很多功能并没有进行扩充实现,如其只支持一个设备的图传,后续可以进行升级改进。软件界面如下所示:

该软件的实现思路非常简单,因此其功能也比较粗狂,bug较多。
软件整体包含两条主要的线程以及一个数据缓冲容器(生产者消费者模式)。在服务器启动之后,软件开启监听服务,监听ESP8266的连接请求,并开启一个消费线程,用于从缓冲容器中获取图像数据(此时应该没有数据),在服务与ESP8266建立连接之后,就开始接收来自ESP8266的图像数据,此时,创建一个数据生产线程,将服务器接收到的数据存放到缓冲容器中,这样,这两个线程一个专门负责将接收到的数据放到缓冲容器中,一个专门负责从缓冲容器中获取数据并将其解码为图像在界面显示。
上位机核心代码:
- /// <summary>
- /// 接收客户端发送的图像数据,并放到缓冲容器中。
- /// </summary>
- /// <param name="proxsocket"></param>
- public void ReceiveClientMessage(object socket)
- {
- int imageCount = 0;
- //服务器端与客户端之间用于通讯的socket
- Socket proxSocket = socket as Socket;
-
- //开辟用来存储客户端发送来的数据的控件
- byte[] dataClient = new byte[640 * 240];
- proxSocket.ReceiveTimeout = 1000 * 300; // 设置接收数据时的阻塞时间为15秒钟
- //接收客户端数据
- while (serverStart)
- {
- StringBuilder imageData = new StringBuilder();
- try
- {
- while (imageCount < 5)
- {
- int countRev = proxSocket.Receive(dataClient, 0, dataClient.Length, SocketFlags.None);
- string revDate = StringUtils.ToHexStrFromByte(dataClient, countRev);
- if (revDate.Length>0)
- {
- imageData.Append(revDate);
- imageCount++;
- AppendToMessage(revDate);
- }
- }
- jpegQueue.Add(imageData.ToString());
- imageCount = 0;
- }
- catch (SocketException e)
- {
- if (e.ErrorCode == 10060)
- {
- continue;
- }
- else
- {
- //客户端非正常退出。
- AppendToMessage(string.Format("客户端:{0}非正常退出。", proxSocket.RemoteEndPoint.ToString()));
- return; //线程终结
- }
- }
-
- }
- }
-
- /// <summary>
- /// 从容器中获取图形数据,并解析为图像显示
- /// </summary>
- private void createImg()
- {
- string imgData = "";
- while (serverStart)
- {
- string imgListData = jpegQueue.Take();
- imgData = imgData + imgListData;
- while (imgData.IndexOf("FF D8")!=-1)
- {
- int startIndex = imgData.IndexOf("FF D8");
- int endIndex = imgData.IndexOf("FF D9");
- if (endIndex != -1)
- {
- string jpeg = imgData.Substring(startIndex,endIndex+5-startIndex);
- imgData = imgData.Substring(endIndex+5);
- Bitmap imageBitmap = StringUtils.GetJpegImage(jpeg.Replace(" ", ""));
- if (imageBitmap != null)
- {
- Image img = Image.FromHbitmap(imageBitmap.GetHbitmap());
- if (this.JpgImage.InvokeRequired)
- {
- JpgImage.Invoke(new Action<Image>(s =>
- {
- JpgImage.Image = s;
- }), img);
- }
- else
- {
- this.JpgImage.Image = img;
- }
- }
- else
- {
- continue;
- }
- }
- else
- {
- break;
- }
-
- }
- }
- }

图像数据的解析也非常简单,根据JPEG图像的特点,其开头为FFD8,结尾为FFD9,我们通过查找这两个标志位,其中间的数据就是整帧的图像数据,我们将其这些数据转化位Bitmap位图进行显示就可以了。显示效果如下图:

上图中可以看出,OV2640采集到的图像数据,最终经过ESP8266发送到了上位机,并成功显示出来,只是受限于串口速率,大概3秒才会传输完一帧数据,在实际使用时,也可以发现,上位机数据缓冲容器时常都是空的,数据生产线程受限于串口速率,导致数据的生产远远小于数据的消耗,表现出来就是几秒才会刷新一帧数据。
本文主要基于STM32、OV2640以及ESP8266完成图像的网络传输,本文受启发并参考了博文:ESP8266+STM32F407+OV7670实现图片传输,在此对该文作者表示深深的感谢。
本文下位机图像数据采集以及上位机图像数据解析源码如下:
无线图传下位机源码:https://download.csdn.net/download/sssxlxwbwz/85251144
无线图传上位机源码: https://download.csdn.net/download/sssxlxwbwz/85251105
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。