1. 程式人生 > >家庭IOT監測之攝像頭資料上傳ONENET

家庭IOT監測之攝像頭資料上傳ONENET

本篇目標:將攝像頭OV7670的照片資料,轉換成BMP二進位制,上傳到ONENET平臺,用於遠端監測。

材料準備:

  • 之前移植的溫溼度及紅外修改工程:(溫溼度及紅外修改工程),繼續往裡面移植攝像頭驅動上傳程式碼。
  • STM32F407最終攝像頭上傳ONENET平臺工程:(STM32F4攝像頭資料上傳onenet),裡面包含溫溼度,紅外感應,攝像頭照片按一定時間週期上傳ONENET平臺。

攝像頭OV7670硬體連線與介面

移植攝像頭等相關驅動

用keil開啟準備材料1的stm32f407_iot工程,往裡面新增程式碼,驅動攝像頭OV7670:

  • 將準備材料最終修改中的攝像頭驅動資料夾(ov7670)與(rgb2bmp)拷貝到stm32f407_iot標準工程dev資料夾下。
  • 在Manage Project Items新增資料夾ov7670與rgb2bmp,然後新增剛才對應拷貝資料夾下的C檔案。
  • 在Options->C/C++->Include Paths新增dev下複製資料夾ov7670與rgb2bmp的路徑。
  • 需要修改ov7670.h與sccb.h檔案中對應的IO引腳,修改成自己對應的引腳口,應該用註釋標出。
  • 仔細檢查ov7670.c與sccb.c檔案中的引腳初始化函式,確認相關RESET、PWDN、SCCB等引腳初始化正確;修改dcmi.c的My_DCMI_Init函式中對於DCMI相關引腳的初始化,與自己的引腳對應。
  • 在main.c中新增標頭檔案:
#include 
"ov7670.h" #include "dcmi.h" #include "ov7670test.h" #include "rgb2bmp.h"
  • 向main函式中新增程式碼,如下:
    //....

    /* 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 }
  • 編譯通過即可,這樣就像上一章一樣,移植成功了ov7670的驅動程式碼,即可使用ov7670攝像頭了。

RGB轉BMP程式碼

在修改之前,先來了解一下,攝像頭的資料和BMP檔案的資料有哪些不同:

  1. 攝像頭資料分很多種,RGB565、RGB555、RGB444、YUV、YCbCr等,這裡選取的是RGB565(常用於液晶屏顯示直接輸出)。RGB565包含16位,如下:
    這裡寫圖片描述
  2. BMP格式資料由幾部分組成:
    (1)bmp檔案頭:共包含有14個位元組:
    這裡寫圖片描述
    (2)點陣圖資訊頭:共包含40個位元組;
    這裡寫圖片描述
    (3)調色盤:單色,16色,256色包含調色盤;16位,24位,32位不包含調色盤;
    (4)點陣圖資料;

現在來修改程式碼:

  • 程式碼攝像頭將獲取到的RGB565資料存在了陣列camera_buffer[]中,要將資料轉換成BMP檔案,需要增加bmp檔案頭和資訊頭共54位元組;
  • 定位檔案ov7670.c的17行camera_buffer陣列,陣列增加54位元組,修改成:
uint16_t camera_buffer[PIC_WIDTH*PIC_HEIGHT+27]={0};
  • 定位ov7670.h的23行,同樣修改成:
extern uint16_t camera_buffer[PIC_WIDTH*PIC_HEIGHT+27];
  • 修改DMA開始地址與資料長度,往後移54個位元組,定位ov7670.c的88行,修改DCMI_DMA_Init傳入引數:
DCMI_DMA_Init((uint32_t)&camera_buffer+54,(sizeof(camera_buffer)-54)/4,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Enable);
  • 這樣一來陣列camera_buffer前54位元組放bmp資料頭,因為是16位點陣圖,沒有調色盤,接下來的位元組都放ov7670的rgb565資料。
  • 呼叫rgb2bmp.c中的rgb565tobmp()函式即可將camera_buffer陣列修改成二進位制bmp檔案。

這裡貼出RGB2BMP檔案的程式碼:
rgb2bmp.c:

//主函式
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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 <stdio.h>

#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平臺上了:

  • 定位net_io.c檔案第374行,新增標頭檔案:
#include "dcmi.h"
  • 修改TIM3_IRQHandler中斷函式:
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();
}  
  • 定位dcmi.c檔案,新增標頭檔案:
#include "onenet.h"
  • 定位dcmi.c檔案第78行,在DMA2_Stream1_IRQHandler中斷中置位上傳圖片標誌:
oneNetInfo.sendData = SEND_TYPE_PICTURE;
  • 定位main函式,在135行中新增如下函式:
                //....

                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");

                //....
  • 定位onenet.c,新增標頭檔案:
#include "ov7670.h"
  • 定位onenet.c第211行,修改替換OneNet_SendData_Picture參入函式如下:
OneNet_SendData_Picture(devid, (char *)camera_buffer, 320*200*2+54);
  • 優化onenet驅動部分函式,定位EdpKit.c第369的EDP_PacketSaveData函式,將第二個引數從int16型改為int32,防止出現圖片過大而出現解析問題導致上傳不了的問題:
uint8 EDP_PacketSaveData(const int8 *devid, int32 send_len, int8 *type_bin_head, SaveDataType type, EDP_PACKET_STRUCTURE *edpPacket)
  • 同樣,在EdpKit.h第112行如上修改成一樣:
uint8 EDP_PacketSaveData(const int8 *devid, int32 send_len, int8 *type_bin_head, SaveDataType type, EDP_PACKET_STRUCTURE *edpPacket);
  • 定位EdpKit.c第372行的remain_len變數,同樣改為int32型:
    int32 remain_len = 0;
  • 編譯,通過後燒寫到stm32f407中,觀察串列埠列印,是否上傳照片工程:
    這裡寫圖片描述
  • 登入onenet平臺檢視資料,是否有照片資料上傳,如圖:

這裡寫圖片描述

這裡寫圖片描述

ps:若是發現上傳的照片有垂直,水平顛倒,可以定位ov7670config.h檔案第82行,0x1E暫存器:

{0x1e, 0x27},//0x07不翻轉;0x17垂直翻轉;0x27水平翻轉;0x37水平垂直翻轉

ONENET修改應用加入照片

進入自己onenet裝置的應用管理,編輯:

  • 選擇元件庫,圖片;
  • 屬性圖片型別選擇圖片格式的資料流;
  • 資料流選擇剛才上傳照片的資料流;
  • 調整圖片大小合適即可;
  • 點擊發布即可;
    在自己的應用主頁就可以看到自己的應用資料了,也可以把連結傳送給別人檢視,如圖:
    這裡寫圖片描述

小結:關於攝像頭上傳資料到ONENET的程式碼中,有許多可以優化的地方,比如:
(1)可以把攝像頭的啟動函式DCMI_Start()放在紅外外部中斷裡面,紅外一感應到範圍裡面有人,就用攝像頭拍攝下來。
(2)現在用的是WIFI的UART介面,速度只有115200,傳輸圖片需要10s左右之久,所以可以將WIFI改為SPI介面與STM32F407相連,可以大大地加快傳輸速率。
(3)想過用來傳輸視訊流,但是stm32f407有點不夠用,RAM也比較小,如果換成ARM,再用H.264壓縮,可以大大降低資料傳輸壓力,再用WIFI視訊視訊流的傳輸,初步設想,後期若是視訊,再更新部落格。

以上移植基本上就完成了,結合以前的資料,就已經把溫溼度、紅外感應資料以及攝像頭照片資料都上傳到ONENET平臺了,可以達到一個簡單的監控的目的了,但只是初步的實現,在除錯過程中還存在一些不惹人注意的bug,所以需要耐心多次測試修改,加油,共勉~