本篇目标:将摄像头OV7670的照片数据,转换成BMP二进制,上传到ONENET平台,用于远程监测。
材料准备:
这里的摄像头OV7670与上一章的一致,详细可以见(家庭IOT监测之摄像头OV7670测试)。
用keil打开准备材料1的stm32f407_iot工程,往里面添加代码,驱动摄像头OV7670:
#include "ov7670.h"
#include "dcmi.h"
#include "ov7670test.h"
#include "rgb2bmp.h"
//....
/* WIFI模块IO初始化配置 */
NET_DEVICE_IO_Init();
/* 摄像头OV7670相关初始化配置 */
if (OV7670_Init() != 0) //新添加
{
printf("Ov7670 Init Failed.\r\n");
}
else
{
printf("Ov7670 Init Succeed.\r\n");
#ifdef OV7670_DBG
OV7670_USART_Init();
#endif
}
在修改之前,先来了解一下,摄像头的数据和BMP文件的数据有哪些不同:
现在来修改代码:
uint16_t camera_buffer[PIC_WIDTH*PIC_HEIGHT+27]={0};
extern uint16_t camera_buffer[PIC_WIDTH*PIC_HEIGHT+27];
DCMI_DMA_Init((uint32_t)&camera_buffer+54,(sizeof(camera_buffer)-54)/4,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Enable);
这里贴出RGB2BMP文件的代码:
rgb2bmp.c:
//主函数
#include
#include
#include
#include "rgb2bmp.h"
static void RGBtoBMP(char *bmp_buffer, int nWidth, int nHeight, char bits)
{
BmpHead m_BMPHeader;
char bfType[2] = { 'B','M' };
m_BMPHeader.imageSize = bits * nWidth*nHeight + 54;
m_BMPHeader.blank = 0;
m_BMPHeader.startPosition = 54;
memcpy(bmp_buffer, bfType, sizeof(bfType));
bmp_buffer += sizeof(bfType);
memcpy(bmp_buffer, &m_BMPHeader.imageSize, sizeof(m_BMPHeader.imageSize));
bmp_buffer += sizeof(m_BMPHeader.imageSize);
memcpy(bmp_buffer, &m_BMPHeader.blank, sizeof(m_BMPHeader.blank));
bmp_buffer += sizeof(m_BMPHeader.blank);
memcpy(bmp_buffer, &m_BMPHeader.startPosition, sizeof(m_BMPHeader.startPosition));
bmp_buffer += sizeof(m_BMPHeader.startPosition);
InfoHead m_BMPInfoHeader;
m_BMPInfoHeader.Length = 40;
m_BMPInfoHeader.width = nWidth;
m_BMPInfoHeader.height = nHeight;
m_BMPInfoHeader.colorPlane = 1;
m_BMPInfoHeader.bitColor = BMP_BITS;
m_BMPInfoHeader.zipFormat = 0;
m_BMPInfoHeader.realSize = bits * nWidth*nHeight;
m_BMPInfoHeader.xPels = 2835;
m_BMPInfoHeader.yPels = 2835;
m_BMPInfoHeader.colorUse = 0;
m_BMPInfoHeader.colorImportant = 0;
memcpy(bmp_buffer, &m_BMPInfoHeader.Length, sizeof(m_BMPInfoHeader.Length));
bmp_buffer += sizeof(m_BMPInfoHeader.Length);
memcpy(bmp_buffer, &m_BMPInfoHeader.width, sizeof(m_BMPInfoHeader.width));
bmp_buffer += sizeof(m_BMPInfoHeader.width);
memcpy(bmp_buffer, &m_BMPInfoHeader.height, sizeof(m_BMPInfoHeader.height));
bmp_buffer += sizeof(m_BMPInfoHeader.height);
memcpy(bmp_buffer, &m_BMPInfoHeader.colorPlane, sizeof(m_BMPInfoHeader.colorPlane));
bmp_buffer += sizeof(m_BMPInfoHeader.colorPlane);
memcpy(bmp_buffer, &m_BMPInfoHeader.bitColor, sizeof(m_BMPInfoHeader.bitColor));
bmp_buffer += sizeof(m_BMPInfoHeader.bitColor);
memcpy(bmp_buffer, &m_BMPInfoHeader.zipFormat, sizeof(m_BMPInfoHeader.zipFormat));
bmp_buffer += sizeof(m_BMPInfoHeader.zipFormat);
memcpy(bmp_buffer, &m_BMPInfoHeader.realSize, sizeof(m_BMPInfoHeader.realSize));
bmp_buffer += sizeof(m_BMPInfoHeader.realSize);
memcpy(bmp_buffer, &m_BMPInfoHeader.xPels, sizeof(m_BMPInfoHeader.xPels));
bmp_buffer += sizeof(m_BMPInfoHeader.xPels);
memcpy(bmp_buffer, &m_BMPInfoHeader.yPels, sizeof(m_BMPInfoHeader.yPels));
bmp_buffer += sizeof(m_BMPInfoHeader.yPels);
memcpy(bmp_buffer, &m_BMPInfoHeader.colorUse, sizeof(m_BMPInfoHeader.colorUse));
bmp_buffer += sizeof(m_BMPInfoHeader.colorUse);
memcpy(bmp_buffer, &m_BMPInfoHeader.colorImportant, sizeof(m_BMPInfoHeader.colorImportant));
bmp_buffer += sizeof(m_BMPInfoHeader.colorImportant);
}
void rgb565tobmp(char *rgb_buffer, unsigned short nWidth, unsigned short nHeight)
{
char bits = BMP_BITS / 8;
int i = 0;
int j = 0;
unsigned char R,G,B;
unsigned short RGB555,RGB565;
char *rgb_buff;
rgb_buff = rgb_buffer;
rgb_buffer += 54;
/* RGB565转RGB555 */
for (i = 0; i < nHeight; i++)
{
for (j = 0; j < nWidth; j++)
{
/* 读取RGB565 */
RGB565 = (*(rgb_buffer+1)<<8 | *rgb_buffer);
/* 分别提取R、G、B数据 */
B = RGB565 & 0x001f;
G = (RGB565 >> 6) & 0x001f;
R = (RGB565 >> 11) & 0x001f;
/* 转换成RGB555数据 */
RGB555 = (R << 10) | (G << 5) | (B);
/* 写入数组 */
*rgb_buffer = RGB555;
*(rgb_buffer+1) = RGB555 >> 8;
rgb_buffer += 2;
}
}
/* 将BMP文件头和信息头写入数组 */
RGBtoBMP(rgb_buff, nWidth, nHeight, bits);
}
rgb2bmp.h:
//rgb2bmp.h文件
#include
#define BMP_BITS 16
typedef unsigned char BYTE;
typedef unsigned short WORD;
// BMP图像各部分说明如下
/***********
第一部分 位图文件头
该结构的长度是固定的,为14个字节,各个域的依次如下:
2byte :文件类型,必须是0x4d42,即字符串"BM"。
4byte :整个文件大小
4byte :保留字,为0
4byte :从文件头到实际的位图图像数据的偏移字节数。
*************/
typedef struct
{ long imageSize;
long blank;
long startPosition;
}BmpHead;
/*********************
第二部分 位图信息头
该结构的长度也是固定的,为40个字节,各个域的依次说明如下:
4byte :本结构的长度,值为40
4byte :图像的宽度是多少象素。
4byte :图像的高度是多少象素。
2Byte :必须是1。
2Byte :表示颜色时用到的位数,常用的值为1(黑白二色图)、4(16色图)、8(256色图)、24(真彩色图)。
4byte :指定位图是否压缩,有效值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS。Windows位图可采用RLE4和RLE8的压缩格式,BI_RGB表示不压缩。
4byte :指定实际的位图图像数据占用的字节数,可用以下的公式计算出来:
图像数据 = Width' * Height * 表示每个象素颜色占用的byte数(即颜色位数/8,24bit图为3,256色为1)
要注意的是:上述公式中的biWidth'必须是4的整数倍(不是biWidth,而是大于或等于biWidth的最小4的整数倍)。
如果biCompression为BI_RGB,则该项可能为0。
4byte :目标设备的水平分辨率。
4byte :目标设备的垂直分辨率。
4byte :本图像实际用到的颜色数,如果该值为0,则用到的颜色数为2的(颜色位数)次幂,如颜色位数为8,2^8=256,即256色的位图
4byte :指定本图像中重要的颜色数,如果该值为0,则认为所有的颜色都是重要的。
***********************************/
typedef struct
{
long Length;
long width;
long height;
WORD colorPlane;
WORD bitColor;
long zipFormat;
long realSize;
long xPels;
long yPels;
long colorUse;
long colorImportant;
/* void show()
{
printf("infoHead Length:%dn",Length);
printf("width&height:%d*%dn",width,height);
printf("colorPlane:%dn",colorPlane);
printf("bitColor:%dn",bitColor);
printf("Compression Format:%dn",zipFormat);
printf("Image Real Size:%dn",realSize);
printf("Pels(X,Y):(%d,%d)n",xPels,yPels);
printf("colorUse:%dn",colorUse);
printf("Important Color:%dn",colorImportant);
}*/
}InfoHead;
/***************************
第三部分 调色盘结构 颜色表
对于256色BMP位图,颜色位数为8,需要2^8 = 256个调色盘;
对于24bitBMP位图,各象素RGB值直接保存在图像数据区,不需要调色盘,不存在调色盘区
rgbBlue: 该颜色的蓝色分量。
rgbGreen: 该颜色的绿色分量。
rgbRed: 该颜色的红色分量。
rgbReserved:保留值。
************************/
typedef struct
{ BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
/* void show(void)
{
printf("Mix Plate B,G,R:%d %d %dn",rgbBlue,rgbGreen,rgbRed);
}*/
}RGBMixPlate;
void rgb565tobmp(char *rgb_buffer, unsigned short nWidth, unsigned short nHeight);
准备好了转换工作,就可以将BMP传输到ONENET平台上了:
#include "dcmi.h"
void TIM3_IRQHandler(void)
{
//清中断标识
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
//---------------- 中断处理 ------------------//
if(oneNetInfo.netWork == 1)
{
//修改添加判断条件,在上传的照片过程中等待
if (oneNetInfo.sendData != SEND_TYPE_PICTURE)
{
net_send_time++;
}
if (net_send_time == 1)
{
oneNetInfo.sendData = SEND_TYPE_DATA;
LED2_TOGGLE;
}
else if ((net_send_time % 25) == 0) //每25s发送心跳请求
{
oneNetInfo.sendData = SEND_TYPE_HEART;
LED2_TOGGLE;
}
else if (net_send_time == NET_TIME_DELAY/2)
{
//修改添加判断条件,只有在空闲的时候在启动摄像头,否则等待
if (oneNetInfo.sendData == SEND_TYPE_OK)
{
//启动摄像头拍照,等待DMA传输
DCMI_Start();
LED2_TOGGLE;
}
}
else if (net_send_time == NET_TIME_DELAY)
{
net_send_time = 0;
}
}
LED1_TOGGLE;
OneNet_Check_Heart();
}
#include "onenet.h"
oneNetInfo.sendData = SEND_TYPE_PICTURE;
//....
case SEND_TYPE_PICTURE:
//新添加
#ifdef OV7670_DBG
ShanWai_SendCamera(camera_buffer+27, PIC_WIDTH, PIC_HEIGHT);
#endif
rgb565tobmp((char *)camera_buffer, PIC_WIDTH, PIC_HEIGHT);
oneNetInfo.sendData = OneNet_SendData(FORMAT_TYPE2, NULL, NULL, NULL, 0);
printf("\r\nOnenet Picture Ready.\r\n");
//....
#include "ov7670.h"
OneNet_SendData_Picture(devid, (char *)camera_buffer, 320*200*2+54);
uint8 EDP_PacketSaveData(const int8 *devid, int32 send_len, int8 *type_bin_head, SaveDataType type, EDP_PACKET_STRUCTURE *edpPacket)
uint8 EDP_PacketSaveData(const int8 *devid, int32 send_len, int8 *type_bin_head, SaveDataType type, EDP_PACKET_STRUCTURE *edpPacket);
int32 remain_len = 0;
ps:若是发现上传的照片有垂直,水平颠倒,可以定位ov7670config.h文件第82行,0x1E寄存器:
{0x1e, 0x27},//0x07不翻转;0x17垂直翻转;0x27水平翻转;0x37水平垂直翻转
进入自己onenet设备的应用管理,编辑:
小结:关于摄像头上传数据到ONENET的代码中,有许多可以优化的地方,比如:
(1)可以把摄像头的启动函数DCMI_Start()放在红外外部中断里面,红外一感应到范围里面有人,就用摄像头拍摄下来。
(2)现在用的是WIFI的UART接口,速度只有115200,传输图片需要10s左右之久,所以可以将WIFI改为SPI接口与STM32F407相连,可以大大地加快传输速率。
(3)想过用来传输视频流,但是stm32f407有点不够用,RAM也比较小,如果换成ARM,再用H.264压缩,可以大大降低数据传输压力,再用WIFI视频视频流的传输,初步设想,后期若是视频,再更新博客。
以上移植基本上就完成了,结合以前的数据,就已经把温湿度、红外感应数据以及摄像头照片数据都上传到ONENET平台了,可以达到一个简单的监控的目的了,但只是初步的实现,在调试过程中还存在一些不惹人注意的bug,所以需要耐心多次测试修改,加油,共勉~