基于STM32、OV2640及ESP8266的无线图传

一、简介:

        本文利用STM32F407单片机、OV2640摄像机模块以及ESP8266 WIFI模块,并基于C#编写的TCP上位机服务,来实现图像的无线传输。

        本文受启发于博客:ESP8266+STM32F407+OV7670实现图片传输,在此感谢该文作者。与该文不同的是,本文采用的摄像机模块是0V2640,传输的数据是压缩之后的jpeg格式的图像数据,而不是像上文博主那样,将RGB565数据直接传输到上位机。此外,本文存在和上文博主同样的问题,即采用串口传输方式,数据传输速率过低,实际应用中,发现3秒左右才能够传出一帧320*240的图片。本文接下来对整个图传功能中使用到的模块一一进行详细介绍。

二、DCMI+OV2640摄像头

        在整个图传项目中,使用到的最核心的模块就是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

         ESP8266是比较常见的WIFI模块,该模块有三种不同的工作模式,即softAP 模式station 模式softAP + station 共存模式。(SoftAP:即无线接入点,是一个无线网络的中心节点,通常使用的无线路由器就是一个无线接入点;Station:即无线终端,是一个无线网络的终端端。)

        本文我们将ESP8266作为客户端,并将其传输模式设置为透传,让其连接位于电脑的TCP上位机服务器,然后将OV2640采集的图像数据通过单片机传输给ESP8266模块,并采用WIFI发送给上位机。ESP8266的配置过程如下:

  • AT+RESTORE                                                      #恢复出厂设置
  • AT+CWMODE=1                                                  #设置ESP8266工作模式为STA
  • AT+RST                                                                #复位
  • AT+CWJAP="路由器账号","密码"                         #连接路由器
  • AT+CIPMODE=1                                                  #设置透传模式
  • AT+CIPSTART="TCP","192.168.6.117",8266      #连接TCP服务器(上位机)
  • AT+CIPSEND                                                       #开启透传

        此处要注意,本文的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()可以对这一帧数据进行处理,所谓的处理在此处其实就是将数据通过串口发送给ESP8266ESP8266模块会将数据发送给上位机,并由上位机解析然后显示。

五、TCP服务上位机

        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

        

        

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注