完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本文虽然以串口通讯的方式进行数据传输,但是建议想要实现实时视频显示的朋友:想要提高无线传输的速率,必须放弃串口通讯的方式,采用如SPI或SDIO的通讯方式作为STM32单片机与ESP8266(或其他无线模块)进行通讯。
1. 硬件结构 本文使用到的硬件设备主要为STM32F407单片机、ESP8266无线WIFI模块、OV7670摄像头、LCD显示屏幕以及计算机。 上图中的LCD显示屏幕只是为了将OV7670采集到的图片在下位机进行显示,与图片传输没有什么关系,并且若增加LCD显示图像功能的话,会进一步影响图片传输的速率。因此不需要在下位机显示的朋友可以不用LCD显示屏幕,直接将图片数据通过ESP8266传输到上位机进行显示。 工作方式: 此处对上图硬件结构中各个模块是如何工作的进行简单的介绍,首先OV7670摄像机模块在单片机的驱动下,采集图像,然后单片机通过串口将采集到的图像传输给ESP8266无线WIFI模块,该模块已经提前设置为无线透传模式(后文会介绍无线透传模式的设置方式),在透传模式下,ESP8266会将单片机串口发送来的所有数据,以无线的方式自动发送给与其连接好的上位机软件,上位机软件通过对接收到的图像数据进行处理,就能够显示出OV7670采集到的图片,本文使用到的上位机软件为C#语言编写,通过TCP/IP通讯方式与ESP8266进行通讯,并可以将传输到的数据处理成图片进行显示,后文会对上位机软件进行详细介绍。 2. STM32驱动OV7670采集图片 本文没有使用DCMI接口来驱动摄像机,而是使用IO口,实现OV7670图像数据的采集,而且其采集速率可以达到每秒24帧以上。以下为OV7670的IO口驱动代码: /初始化GPIO u8 OV7670_Init(void) { u8 temp; u16 i=0; GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB |RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE |RCC_AHB1Periph_GPIOG, ENABLE);//时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6;//D1 2 3 4 5 6 7数据线 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_Init(GPIOE, &GPIO_InitStructure); //IO口初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //D0数据线 GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//VSYNC 中断输入线 GPIO_Init(GPIOA, &GPIO_InitStructure); /*output*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;//RRST及PCLK GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //WRST GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//WEN及OE GPIO_Init(GPIOG, &GPIO_InitStructure); SCCB_Init(); //SCCB(与IIC类似)IO口初始化 if(SCCB_WR_Reg(0x12,0x80))return 1; //复位SCCB delay_ms(50); / //读取产品ID //读取产品型号 temp=SCCB_RD_Reg(0x0b); if(temp!=0x73)return 2; temp=SCCB_RD_Reg(0x0a); if(temp!=0x76)return 2; / //初始化序列 (该部分代码非常关键,也是整个底层驱动的核心。由厂家提供) for(i=0;i《sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0]);i++) { SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]); } return 0x00; //ok } 根据OV7670的驱动代码,正确连接接线,搭配LCD显示屏幕,就可以显示摄像机采集到的图像,速率大于24帧每秒,下图为LCD摄像头视频显示。 在使用杜邦线进行接线的时候,一定注意要将数据线和信号线分开各自单独捆起来,否则信号之间会发生干扰,导致花屏现象。如果使用排针插口,则不存在这个问题。 3. ESP8266无线透传模式设置 一般情况下,在网上购买的ESP8266模块内置AT固件库,用户使用串口助手通过简单的AT指令就可以将ESP8266设置为不同的模式(总共有3种模式)。但是,如果用户购买的模块没有内置固件库,则需要用户自己烧写固件。烧写固件库很简单,网上由很多教程,本文重点不在此,因此不做过多介绍。 ESP8266透传模式设置步骤: 设置为客户端模式:AT+CWMODE=1 设置工作模式 查询是否设置成功:AT+CWMODE? 查询工作模式 加入AP: AT+CWJAP=“XXXX”,“XXXX”,上述指令中,使用者应该根据实际情况,填写所要连接的热点或者WIFI的名称以及密码 重启ESP8266: AT+RST 重启 获取本模块IP看一下是否连接上了: AT+CIFSR 设置为透传模式:AT+CIPMODE=1 连接到你自己的服务器(可以用网络调试助手进行测试): AT+CIPSTART=“TCP”,“IP地址”,端口 进入透传:AT+CIPSEND ESP8266模块按照上述步骤与PC连接好并设置为透传模式之后,就可以与PC进行通讯。本文使用STM32采集OV7670的图像,然后利用串口将采集到的图像数据传输给ESP8266,ESP8266会自动将串口传输来的数据发送给PC端的上位机软件,在上位机中就可以实现图片的显示。 4. STM32主函数图像数据更新代码 以下代码为STM32主程序,包括OV7670图像帧的刷新以及数据发送。 //OV7670图像刷新函数 void camera_refresh(void) { u32 j; u16 color; u8 re[640]={0}; u16 flag=0; if(ov_sta) { LCD_Scan_Dir(U2D_L2R); //LCD扫描方式 if(lcddev.id==0X1963)LCD_Set_Window((lcddev.width-240)/2,(lcddev.height-320)/2,240,320);//½«ÏÔʾÇøÓòÉèÖõ½ÆÁÄ»ÖÐÑë else if(lcddev.id==0X5510||lcddev.id==0X5310)LCD_Set_Window((lcddev.width-320)/2,(lcddev.height-240)/2,320,240);//½«ÏÔʾÇøÓòÉèÖõ½ÆÁÄ»ÖÐÑë LCD_WriteRAM_Prepare(); OV7670_RRST=0; //开始复位读指针 OV7670_RCK_L; OV7670_RCK_H; OV7670_RCK_L; OV7670_RRST=1; //复位读指针结束 OV7670_RCK_H; for(j=0;j《76800;j++) //根据分辨率,读取数据。 { OV7670_RCK_L; color=OV7670_DATA; re[flag++]=color; OV7670_RCK_H; color《《=8; OV7670_RCK_L; color|=OV7670_DATA; re[flag++]=color; OV7670_RCK_H; LCD-》LCD_RAM=color; if(flag==640) { Send_data_3(re); //使用串口3将数据传输给ESP8266,每次发送640个字节。 flag=0; } } ov_sta=0; //清除帧中断标志。 ov_frame++; LCD_Display_Dir(0); } } //LCD相关模式参数 const u8*LMODE_TBL[5]={“Auto”,“Sunny”,“Cloudy”,“Office”,“Home”}; const u8*EFFECTS_TBL[7]={“Normal”,“Negative”,“B&W”,“Redish”,“Greenish”,“Bluish”,“Antique”}; //7ÖÖÌØЧ int main() { VO7670_flag=0; SysTick_Init(168); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //ÖжÏÓÅÏȼ¶·Ö×é ·Ö2×é LED_Init(); KEY_Init(); USART3_Init(115200); USART1_Init(115200); LCD_Init(); //LCD初始化函数 POINT_COLOR=RED; //画笔颜色设置 LCD_ShowString(30, 70, 200, 16, 16, “OV7670 Test”); while(OV7670_Init()) { LCD_ShowString(30, 230, 200, 16, 16, “OV7670 Error!”); delay_ms(200); LCD_Fill(30,230,239,246,WHITE); delay_ms(200); } LCD_ShowString(30, 230, 200, 16, 16, “OV7670 OK!”); delay_ms(1500); OV7670_Light_Mode(0); OV7670_Color_Saturation(2); OV7670_Brightness(2); OV7670_Contrast(2); OV7670_Special_Effects(0); TIM4_Init(20000,7199); EXTI8_Init(); OV7670_Window_Set(12,176,240,320); OV7670_CS=0; LCD_Clear(BLACK); while(1) { if(VO7670_flag==1) //该参数是有上位机控制。可以去掉 { camera_refresh(); //更新显示图像或无线传输图像数据 VO7670_flag=0; } } 5. 上位机软件 上位机主要用于与ESP8266进行无线连接,并将ESP8266发送来的图片数据重新编码为图片并进行显示,本文使用C#语言编写上位机软件,通过TCP/IP协议与ESP8266进行通讯,软件界面如下图所示。 软件界面中间黑色区域为图像显示区域,下方白色TextBox为数据发送区域,左侧TextBox为数据接收显示区域(程序设置为图片数据不显示)。由于下位机单片机采集到的图片数据格式为RGB565,因此上位机内部需要将接收到的RGB565格式的图像数据转化为RGB24,才可以正常将接收到的图片数据显示为图片。 该上位机主要功能包括TCO/IP协议以及图像数据解码,具体代码如下所示: private void Start_Click(object sender, EventArgs e) { panel1.BackColor = System.Drawing.Color.LightSkyBlue; panel4.BackColor = System.Drawing.Color.LightSkyBlue; panel3.BackColor = System.Drawing.Color.LightSteelBlue; //1.创建服务器端用于监听的SOCKET Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.绑定IP和端口 IPAddress ip = IPAddress.Parse(textBox_IP.Text); IPEndPoint ipandport = new IPEndPoint(ip, int.Parse(textBox_Port.Text)); serverSocket.Bind(ipandport); //3.开启监听 serverSocket.Listen(10); //使用线程池技术,服务器端接受客户端的连接 ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect), serverSocket); } /// 《summary》 /// 接受客户端的连接 /// 《/summary》 /// 《param name=“socket”》《/param》 public void AcceptClientConnect(object socket) { //传递过来的服务器端SOCKET Socket serverSocket = socket as Socket; this.AppendToMessage(“服务器开始接受客户端的连接请求。”); //4.服务器端开始接受客户端的连接 //此处服务器端应该一直循环,用来一直接收可能的客户端。 while (true) { //创建代理Socket,用来与客户端进行通讯。 Socket proxsocket = serverSocket.Accept(); //将代理socker放到窗体级别的集合中 proxSocketList.Add(proxsocket); AppendToMessage(string.Format(“客户端:{0}已经连接上。”, proxsocket.RemoteEndPoint.ToString())); //ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMessage),proxsocket); Thread MyThread = new Thread(new ParameterizedThreadStart(ReceiveClientMessage)); MyThread.IsBackground = true; MyThread.Start(proxsocket); Thread.Sleep(100); } } /// 《summary》 /// 接收客户端发送的消息 /// 《/summary》 /// 《param name=“proxsocket”》《/param》 public void ReceiveClientMessage(object socket) { int flag= 0; //服务器端与客户端之间用于通讯的socket Socket proxSocket = socket as Socket; //开辟用来存储客户端发送来的数据的控件 byte[] dataClient = new byte[640*240]; proxSocket.ReceiveTimeout = 100 * 2; //设置接收数据时的阻塞时间为15秒钟 //接收客户端数据 while (true) //总共有240列数据,每列数据为640个字节,两个字节组成一个像素点。 { try { proxSocket.Receive(dataClient, 0, dataClient.Length, SocketFlags.None); } catch (SocketException e) { if (e.ErrorCode == 10060) { hang = 0; lie = 0; flag = 0; continue; } else { //客户端非正常退出。 proxSocketList.Remove(proxSocket); AppendToMessage(string.Format(“客户端:{0}非正常退出。”, proxSocket.RemoteEndPoint.ToString())); return; //线程终结 } } //绘制一列数据 if(flag《240) { Paint_bmp(dataClient); flag++; } else { hang = 0; lie = 0; flag = 0; } } } /// 《summary》 /// 根据接收的数组绘制一副图像 /// 注意:byte[] Data数据中的数据不一定为一副图像的完整数据 /// 若数据不完整,下次调用时将自动完成绘制 /// 《/summary》 /// 《param name=“Data”》《/param》 void Paint_bmp(byte[] Data) { int i = 0; //foreach (byte color in Data) for(i=0;i《Data.Length;i++) { if (isheight) //判断是否为高位 { isheight = false; heightdate = Data[i]; } else { isheight = true; //若为低8位,则转化颜色,并写入bmp Color c = RGB565ToRGB24(heightdate, Data[i]); Write_Color(c); } } } /// 《summary》 /// 将一个16位的RGB565格式颜色转化成RGB24格式颜色,并返回Color类 /// 《/summary》 /// 《param name=“RGB565_H”》《/param》 /// 《param name=“RGB565_L”》《/param》 /// 《returns》《/returns》 Color RGB565ToRGB24(int RGB565_H, int RGB565_L) { int RGB565_MASK_RED = 0xF800; int RGB565_MASK_GREEN = 0x07E0; int RGB565_MASK_BLUE = 0x001F; int RGB565; int R, G, B; RGB565_H 《《= 8; RGB565 = RGB565_H | RGB565_L; R = (RGB565 & RGB565_MASK_RED) 》》 11; G = (RGB565 & RGB565_MASK_GREEN) 》》 5; B = (RGB565 & RGB565_MASK_BLUE); R 《《= 3; G 《《= 2; B 《《= 3; return Color.FromArgb(R, G, B); } /// 《summary》 /// 在bmp中写入一个像素点的颜色,并自动将指针向下一个是像素点移动 /// 当指针移动到像素点的最后一个像素时,将返回图像起点 /// 《/summary》 /// 《param name=“c”》《/param》 void Write_Color(Color c) { if(c.R==0&&c.B==0&c.G==0) { return; } //使用委托在子线程操作主线程界面。 this.Invoke((EventHandler)(delegate { bmp.SetPixel(hang, lie, c); })); if (lie 《 bmp_height - 1) { lie++; } else { lie = 0; this.BeginInvoke((EventHandler)(delegate { pictureBox1.Image = bmp; })); if (hang 《 bmp_width - 1) { hang++; } else { hang = 0; } } } 最终的显示结果如下图所示: 上图完成了最终的图像数据无线传输,并在上位机中完成了图像的解码,其中图像显示区域右侧黑色区域是因为图像显示区域设置的比真实图片的分辨率略大。后续可以进行改进。此外,由于单片机每发送一次数据,除原本的数据之后,貌似好有多余的几个字节的数据,导致图片显示出现些许差错,后续也可以进行改进。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1614 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1541 浏览 1 评论
970 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
682 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1592 浏览 2 评论
1863浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
644浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
515浏览 3评论
531浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
504浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 00:06 , Processed in 0.820067 second(s), Total 79, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号