1. 程式人生 > 實用技巧 >STM32記憶體受限情況下攝像頭驅動方式與影象裁剪的選擇

STM32記憶體受限情況下攝像頭驅動方式與影象裁剪的選擇

1、STM32影象接收介面

使用stm32晶片,128kB RAM,512kB Rom,資源有限,接攝像頭採集影象,這種情況下,記憶體利用制約程式設計。

STM32使用DCMI介面讀取攝像頭,協議如下。行同步訊號指示了一行資料完成,場同步訊號指示了一幀影象傳輸完成。所以出現了兩種典型的資料接收方式,按照行訊號一行一行處理,按照場訊號一次接收一副影象。

2、按行讀取

以網路上流行的野火的demo為例,使用行中斷,用DMA來讀取一行資料。

//記錄傳輸了多少行
static uint16_t line_num =;
//DMA傳輸完成中斷服務函式
void DMA2_Stream1_IRQHandler(void)
{
if ( DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) == SET )
{
/*行計數*/
  line_num++;
  if (line_num==img_height)
  {
  /*傳輸完一幀,計數復位*/
  line_num=;
  }
  /*DMA 一行一行傳輸*/
  OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width**(lcd_height-line_num-)),img_width*/);
  DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
  }
} //幀中斷服務函式,使用幀中斷重置line_num,可防止有時掉資料的時候DMA傳送行數出現偏移
void DCMI_IRQHandler(void)
{
  if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET )
  {
  /*傳輸完一幀,計數復位*/
  line_num=;
  DCMI_ClearITPendingBit(DCMI_IT_FRAME);
  }
}

DMA中斷服務函式中主要是使用了一個靜態變數line_num來記錄已傳輸了多少行資料,每進一次DMA中斷時自加1,由於進入一次中斷就代表傳輸完一行資料,所以line_num的值等於lcd_height時(攝像頭輸出的資料行數),表示傳輸完一幀影象,line_num復位為0,開始另一幀資料的傳輸。line_num計數完畢後利用前面定義的OV2640_DMA_Config函式配置新的一行DMA資料傳輸,它利用line_num變數計算視訊記憶體地址的行偏移,控制DCMI資料被傳送到正確的位置,每次傳輸的都是一行畫素的資料量。

當DCMI介面檢測到攝像頭傳輸的幀同步訊號時,會進入DCMI_IRQHandler中斷服務函式,在這個函式中不管line_num原來的值是什麼,它都把line_num直接復位為0,這樣下次再進入DMA中斷服務函式的時候,它會開始新一幀資料的傳輸。這樣可以利用DCMI的硬體同步訊號,而不只是依靠DMA自己的傳輸計數,這樣可以避免有時STM32內部DMA傳輸受到阻塞而跟不上外部攝像頭訊號導致的資料錯誤。

影象按幀讀取比按行讀取效率更高,那麼為什麼要按行讀取呢?上面的例子是把影象送到LCD,如果是送到記憶體,按幀讀取就需要晶片有很大的記憶體空間。以752*480的解析度為例,需要360kB的RAM空間,遠遠超出了晶片RAM的大小。部分應用不需要攝像頭全尺寸的影象,只需要中心區域,比如為了避免畸變影響一般只用影象中間的部分,那麼按行讀取就有一個好處,讀到一行後,可以把不需要的丟棄,只保留中間部分的影象畫素。

那麼問題來了?為什麼不直接配置攝像頭的屬性,來實現只讀取影象的中間部分呢,全部讀取出來然後在arm的記憶體中裁剪丟棄不要的畫素,第一浪費了讀取時間,第二浪費了讀取的空間。更優的做法是直接配置攝像頭sensor,使用sensor的裁剪功能輸出需要的畫素區域。

3、影象裁剪--使用STM32 crop功能裁剪

STM32F4系列的DCMI介面支援裁剪功能,對攝像頭輸出的畫素點進行擷取,不需要的畫素部分不被DCMI傳入記憶體,從硬體介面一側就丟棄了。

HAL_DCMI_EnableCrop(&hdcmi);
HAL_DCMI_ConfigCrop(&hdcmi, CAM_ROW_OFFSET, CAM_COL_OFFSET, IMG_ROW-, IMG_COL-);

裁剪的本質如下所述,從接收到的資料裡選擇需要的矩形區域。所以STM32 DCMI裁剪功能可以完成節約記憶體,只選取需要的影象存入記憶體的作用。

此方法相比於一次讀一行,然後丟棄首尾部分後把需要的區域影象畫素存入buffer後再讀下一行,避免了時序錯誤,程式碼簡潔了,DCMI硬體計數丟掉不要的畫素,也提高了程式可靠性、可讀性。

成也蕭何敗也蕭何,如上面所述,STM32的crop完成了選取特定區域影象的功能,那麼也要付出代價,它是從接收到的影象資料裡進行選擇的,這意味著那些不需要的資料依然會傳輸到MCU一側,只不過MCU的DCMI對資料進行計數是忽略了它而已,那麼問題就來了,哪些不需要的資料的傳輸會帶來什麼問題呢?

有圖為證,下圖是使用了STM32 crop裁剪的時序圖,通道1啟動採集IO置高,frame中斷里拉低,由於使用dma傳輸,那麼被crop裁剪後dma計數的資料量變少,所以DCMI frame中斷能在行資料傳輸完成前到達,通道1高電平部分就代表一有效解析度的幀的採集時間。通道2 曝光訊號管腳,通道3是行掃描訊號。其中通道1下降沿到通道3下降沿4.5ms。代表微控制器已經收到crop指定尺寸的影象,採集有效區域(crop區域)的影象完成,但是line訊號沒有結束還有很多行沒傳輸,即CMOS和DCMI介面要傳輸752*480影象還沒完成。

舉例說明,如果使用752*480解析度採集影象,你只取中間的360*360視野,有效解析度是360*360,但是匯流排上的資料依然是752*480,所以幀率無法提高,多餘的資料按說就不應該傳輸出來,如何破解,問題追到這裡,STM32晶片已經無能為力了,接下來需要在CMOS一側發力了。

4、影象裁剪--配置COMOS暫存器裁剪

下圖是MT9V034 攝像頭晶片的暫存器手冊,Reg1--4配置CMOS的行列起點和寬度高度。

修改暫存器後,攝像頭CMOS就不再向外傳輸多餘的資料,被裁剪丟棄的資料也不會反應在介面上,所以STM32 DCMI接收到的資料都是需要保留的有效區資料,極大地減少了資料輸出,提高了傳輸效率。本人也在STM324晶片上,實現了220*220解析度120幀的連續採集。

下面是序圖,通道1高電平代表開始採集和一幀結束,不同於使用STM32 的crop裁剪,使用CMOS暫存器裁剪有效視窗,使得幀結束時行訊號也同時結束,後續沒有任何需要傳輸的行資料。

5、一幀資料一次性傳輸

一幀資料一次全部讀入到MCU的方式,其實是最簡單的驅動編寫方式,但是對於沒有壓縮功能的cmos晶片來說,一般都無力實現。對部分有jpg壓縮功能的cmos晶片而言,比如OV2640可以使用這種方式,一次性讀出一幀影象。

__align() u32 jpeg_buf[jpeg_buf_size];    //JPEG buffer
//JPEG 格式
const u16 jpeg_img_size_tbl[][]=
{
,, //QCIF
,, //QQVGA
,, //CIF
,, //QVGA
,, //VGA
,, //SVGA
,, //XGA
,, //SXGA
,, //UXGA
};

//DCMI 接收資料
void DCMI_IRQHandler(void)
{
  if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)// 一幀資料
  {
    jpeg_data_process();
    DCMI_ClearITPendingBit(DCMI_IT_FRAME);
  }
}