STM32記憶體受限情況下攝像頭驅動方式與影象裁剪的選擇
1、STM32影象接收介面
使用stm32晶片,128kB RAM,512kB Rom,資源有限,接攝像頭採集影象,這種情況下,記憶體利用制約程式設計。
STM32使用DCMI介面讀取攝像頭,協議如下。行同步訊號指示了一行資料完成,場同步訊號指示了一幀影象傳輸完成。所以出現了兩種典型的資料接收方式,按照行訊號一行一行處理,按照場訊號一次接收一副影象。
2、按行讀取
以網路上流行的野火的demo為例,使用行中斷,用DMA來讀取一行資料。
//記錄傳輸了多少行 static uint16_t line_num =0; //DMA傳輸完成中斷服務函式 void DMA2_Stream1_IRQHandler(void) { if ( DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) == SET ) { /*行計數*/ line_num++; if (line_num==img_height) { /*傳輸完一幀,計數復位*/ line_num=0; } /*DMA 一行一行傳輸*/ OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width*2*(lcd_height-line_num-1)),img_width*2/4); DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1); } } //幀中斷服務函式,使用幀中斷重置line_num,可防止有時掉資料的時候DMA傳送行數出現偏移 void DCMI_IRQHandler(void) { if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET ) { /*傳輸完一幀,計數復位*/ line_num=0; 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-1, IMG_COL-1);
裁剪的本質如下所述,從接收到的資料裡選擇需要的矩形區域。所以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、影象裁剪--配置CMOS暫存器裁剪
下圖是MT9V034 攝像頭晶片的暫存器手冊,Reg1--4配置CMOS的行列起點和寬度高度。
修改暫存器後,攝像頭CMOS就不再向外傳輸多餘的資料,被裁剪丟棄的資料也不會反應在介面上,所以STM32 DCMI接收到的資料都是需要保留的有效區資料,極大地減少了資料輸出,提高了傳輸效率。本人也在STM324晶片上,實現了220*220解析度120幀的連續採集。
下面是序圖,通道1高電平代表開始採集和一幀結束,不同於使用STM32 的crop裁剪,使用CMOS暫存器裁剪有效視窗,使得幀結束時行訊號也同時結束,後續沒有任何需要傳輸的行資料。
5、一幀資料一次性傳輸
一幀資料一次全部讀入到MCU的方式,其實是最簡單的驅動編寫方式,缺點就是太佔記憶體,但是對於沒有壓縮功能的cmos晶片來說,一般都無力實現。對部分有jpg壓縮功能的cmos晶片而言,比如OV2640可以使用這種方式,一次性讀出一幀影象。
__align(4) u32 jpeg_buf[jpeg_buf_size]; //JPEG buffer //JPEG 格式 const u16 jpeg_img_size_tbl[][2]= { 176,144, //QCIF 160,120, //QQVGA 352,288, //CIF 320,240, //QVGA 640,480, //VGA 800,600, //SVGA 1024,768, //XGA 1280,1024, //SXGA 1600,1200, //UXGA };
//DCMI 接收資料
void DCMI_IRQHandler(void)
{
if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)// 一幀資料
{
jpeg_data_process();
DCMI_ClearITPendingBit(DCMI_IT_FRAME);
}
}
&n