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