图9 OV2640 SVGA模式下图像输出时序图
系统上电后,MCU配置OV2640的工作方式,在OV2640准备好图像后,VSYNC会被拉高一段时间,MCU通过PCLK上升沿中断按字节接收图像数据。接下来我们将对OV2640的初始化配置程序和图像数据缓存程序进行介绍。
2.OV2640程序介绍
初始化配置程序:
iic_init();/*初始化MCU I2C_2,与OV2640 SCCB接口通信*/
ov2640_jpeg_config(JPEG_640x480); /*设置输出图像格式*/
/* 设置COMS参数 */
ov2640_brightness_config(0×40); /*设置亮度模式:亮度+2*/
ov2640_auto_exposure(3); /*设置自动曝光等级 0-4*/
ov2640_contrast_config(0×28,0x0c); /*设置对比度:对比度+2*/
ov2640_black_white_config(0×00); /*设置黑白彩色模式:正常模式*/
ov2640_color_satura
tion(0×68,0×68); /*设置色饱和度:饱和度+2*/
ov2640_light_mode_config(OFFICE); /*设置场景模式:办公室*/
o2640_capture_gpio_init();/*初始化并行传输IO管脚*/
注:以上代码在main.c文件中
图像数据缓存程序:
u8 temp;
EXTI_ClearITPendingBit(EXTI_Line0);/*清除PC0(PCLK)中断*/
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)==0)/*HREF管脚为低*/
return;
temp =(u8)((GPIOC->IDR)>>8&0x00ff);/*读取一个字节图像数据*/
switch(jpg_flag)
{
case0:
if(temp==0xff)/*图像数据以0xff 0xd8开头*/
{
JPEGBuffer[4]=0xff;
jpg_flag=1;
}
break;
case1:
if(temp==0xd8)
{
JPEGBuffer[5]=0xd8;
jpg_flag=2;
JPEGCnt=6;
}
elseif(temp!=0xff)
jpg_flag=0;
break;
case2:
JPEGBuffer[JPEGCnt++]= temp;/*存储数据*/
if(temp==0xff)
jpg_flag=3;
break;
case3:
JPEGBuffer[JPEGCnt++]= temp;/*图像数据以0xff 0xd9结尾*/
if(temp==0xd9)
{
jpg_flag=4;
counter++;
}
elseif(temp!=0xff)
jpg_flag=2;
break;
case4:
break;
}
注:以上代码在websocket.c文件中
在中断函数中通过以上缓存数据即可正确读取每一帧图像的数据了。其中JPEGBuffer为一个全局的图像缓存区,WebSocket数据发送函数中检测到缓存区数据准备完毕后,就可以将图像发送给浏览器了。
在Canvas上绘制图片
Canvas API中有趣的一面就是对图片的支持,我们可以借助drawImage函数,通过多种方法操作图片,drawImage有三种格式:
n drawImage(image, dx, dy):将image URL指定的图片显示在dx,dy位置
n drawImage(image, dx, dy, dw, dh):根据提供的显示宽度(dw)和显示高度(dh)缩放显示图片
n drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh):根据X,Y坐标和宽高(sx,sy,sw,sh)剪裁出图片的一部分显示出来。
下面开看看我们的网页程序里是如何实现绘制图片的吧。
1.首先建立一张画布
2.定义画布的边距、宽高等信息
.img{margin:0 auto;display:block;margin-bottom:10px; width:640px;height:480px;cursor:pointer;}
3.为了在JavaScript中对canvas进行绘制,首先需要通过目标canvas的id获取绘制环境。代码需要通过id获取canvas元素,然后使用此元素的getContext方法获取其二维绘制环境的引用。
var canvas =document.getElementById(‘cam’);
var context =canvas.getContext(’2d’);
4.WebSocket接收到数据,并绘制图像
websocket.onmessage= function (evt)/*收到服务器消息,使用evt.data提取*/
{
var image = new Image();
image.onload= function ()
{
context.clearRect(0,0,canvas.width,canvas.height);/*清除画布矩形区域*/
context.drawImage(image,0,0,canvas.width,canvas.height);/*绘制宽度640px,高度480px的图像*/
}
image.src=URL.createObjectURL(evt.data);/*生成本图像数据的URL信息*/
}
可见我们使用了第二种drawImage格式,指定缩放大小使其与画布大小刚好匹配,而我们从OV2640获取的图像恰好与画布大小相同。
注:以上代码在webpage.c文件中
WebSocket数据传输程序
在上一期的《搭建属于你的在线实时采集系统》中,我们详细介绍了WebSocket的使用和API函数,并分析了握手流程。在本篇文章中将不再赘述,这里将对数据较大的传输程序进行介绍。
if(handshaked)/*握手成功*/
{
uint32jpgLen=0;
uint32 send_len=0;
uint8firstByte=0×82;//FIN=1, opcode=0×02: binary
uint8secondByte=126;//no mask, extend length=2 bytes
uint8 extend[2]={0×00};//extend header
while(jpg_flag!=4);/*图像缓存完毕*/
jpgLen=JPEGCnt;
extend[0]=(jpgLen-4)/256;/*提取payload高8位*/
extend[1]=(jpgLen-4)%256;/*提取payload低8位*/
/*打包websocket数据包*/
JPEGBuffer[0]=firstByte;
JPEGBuffer[1]=secondByte;
JPEGBuffer[2]=extend[0];
JPEGBuffer[3]=extend[1];
while(jpgLen)
{
if(jpgLen>WS_PACKET_LEN)/*长度大于W5500发送缓存区大小(4K默认)*/
{
send(s,(uint8*)(JPEGBuffer+send_len), WS_PACKET_LEN);
send_len+= WS_PACKET_LEN;
jpgLen-= WS_PACKET_LEN;
}
else/*将数据包剩余的字节全部发送出去*/
{
send(s,(uint8*)(JPEGBuffer+send_len),jpgLen);
send_len+=jpgLen;
jpgLen-=jpgLen;
}
}
if(jpg_flag==4)/*发送完毕后,重置图像采集标志位*/
jpg_flag=0;
printf(“.%d”,send_len);/*调试口打印本次数据包长度*/
}
上一篇中,由于数据包长度较小只有7个字节,所以没有使用到扩展长度字节,由于OV2640采集到的图像经过压缩后,一帧图像的数据远大于125个字节,所以就需要用到扩展字节来表征数据长度,经过实测,本系统中OV2640在JPEG_480*640模式下,一帧图像大概12Kbytes左右,所以使用两个扩展字节(16位,最大可表示65535个字节)就可以了。根据上一篇数据包帧格式定义,当数据包第二个字节secondByte的后7位为126时表示使用2字节长度扩展,为127时表示使用8字节长度扩展。这里我们没有使用掩码,所以secondByte的第一位为0,在扩展字节后就是数据包的payload(图像数据)。在发送时,由于W5500每个socket有自己的收发缓存区,在设备初始化时可配置,默认设置4K,所以一帧大小为12K的图像是无法一次发送出去的,程序的后半部分就是将数据包切割分几次发送。W5500也再次展示它硬件协议栈易于使用,快速传输的优越性能。使远程监控轻松实现。
总结
于此,摄像头 + 单片机 + HTML5的组合完成了家庭网络监控的作品。其实很多创意和想法都是源于各种功能组合,非常乐意与你分享我们的程序,希望你能发挥自己的想象力,组合出更多新奇的web功能,体会DIY的价值与乐趣。
程序下载地址:http://wizwiki.net/forum/viewtopic.php?f=91&t=733
``