本帖最后由 dirty 于 2024-6-1 19:56 编辑
本篇讲述摄像头驱动,显示到LCD屏。
一.了解摄像头原理
开发板配备有一颗OV2640摄像头,DVP接口,200W像素。开发板摄像头部分的原理如下。
图1:摄像头原理
引脚功能定义:
PIXCLK输出时钟
XCLK外部时钟输入
VSYNC场同步
HSYNC行同步
D[0:7]并口数据(8bit数据位数大小)
I2C接口:寄存器驱动配置
模块特点:
标准的SCCB接口,兼容12C接口:
RaWRGB,RGB(GRB4:2:2,RGB565/555/444),YUV(4:2:2)和YCbCr(4:2:2)输出格式。
支持UXGA、SXGA、VGA、QVGA、QQVGA、CIF、QCIF等支持自动曝光控制、自动增益控制、自动白平衡,自动消除灯光条纹、自动黑电平校准.图像质量控制包括色饱和度、色相、伽玛、锐度ANTI BLOOM等设置。
支持图像缩放、平移和窗口设置。
支持图像压缩,即可输出JPEG图像数据。
二.软件代码
实现方式:使用DCI接口采集数据,使用前面用过的TLI接口显示采集图像。这里在前面LVGL工程基础上实现按键切换到摄像头图像显示到LCD屏。
1.添加sdk里关于摄像头驱动的源文件,如下图所示
图2:摄像头驱动文件添加
2.摄像头驱动初始化。这里初始化了摄像头sccb引脚,以及dci配置,包含I2C初始化,DMA初始化。之后通过写参数配置摄像头。最后定义了摄像头输出图像大小320*240。显示屏是480*272,因此后面涉及到转换。
/*!
\brief DCI camera initialization
\param[in] none
\param[out] none
\retval 0x00 or 0xFF
*/
uint8_t dci_ov2640_init(void)
{
uint8_t i;
sccb_config();
dci_config();
ckout0_init();
delay_1ms(100);
/* OV2640 reset */
if(dci_byte_write(0xFF, 0x01) != 0) {
return 0xFF;
}
if(dci_byte_write(0x12, 0x80) != 0) {
return 0xFF;
}
delay_1ms(10);
for(i = 0; i < sizeof(ov2640_svga_init_reg_tbl) / 2; i++) {
if(0 != dci_byte_write(ov2640_svga_init_reg_tbl[i][0], ov2640_svga_init_reg_tbl[i][1])) {
return 0xFF;
}
}
delay_1ms(100);
for(i = 0; i < (sizeof(ov2640_rgb565_reg_tbl) / 2); i++) {
if(0 != dci_byte_write(ov2640_rgb565_reg_tbl[i][0], ov2640_rgb565_reg_tbl[i][1])) {
return 0xFF;
}
}
delay_1ms(100);
ov2640_outsize_set(320, 240);
return 0;
}
3.读出摄像头信息,包含厂商,版本和pid.
/*!
\brief read the ov2640 manufacturer identifier
\param[in] ov2640id: pointer to the ov2640 manufacturer struct
\param[out] none
\retval 0x00 or 0xFF
*/
uint8_t dci_ov2640_id_read(ov2640_id_struct *ov2640id)
{
uint8_t temp;
dci_byte_write(0xFF, 0x01);
if(dci_byte_read(OV2640_MIDH, &temp) != 0) {
return 0xFF;
}
ov2640id->manufacturer_id1 = temp;
if(dci_byte_read(OV2640_MIDL, &temp) != 0) {
return 0xFF;
}
ov2640id->manufacturer_id2 = temp;
if(dci_byte_read(OV2640_VER, &temp) != 0) {
return 0xFF;
}
ov2640id->version = temp;
if(dci_byte_read(OV2640_PID, &temp) != 0) {
return 0xFF;
}
ov2640id->pid = temp;
return 0x00;
}
4.配置DMA和 DCI,这里LVGL显示在LAYER0,摄像头画面显示在LAYER1,故这里先将摄像头禁能。
nvic_configuration();
/* DMA interrupt and channel enable */
dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
dma_channel_enable(DMA1, DMA_CH7);
/* DCI enable */
dci_enable();
dci_capture_enable();
delay_1ms(3000);
dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
dma_channel_disable(DMA1, DMA_CH7);
dci_capture_disable();
5.俘获照片数据保存到SDRAM,这里不停止视频流,如果拍照可以把屏蔽的打开
/*!
\brief save image to sdram
\param[in] none
\param[out] none
\retval none
*/
void image_save(void)
{
uint32_t i = 0;
// dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
// dma_channel_disable(DMA1, DMA_CH7);
// dci_capture_disable();
/* save image to sdram */
for(i = 0; i < 32640; i++) {
*(uint32_t *)(0xC0800000 + 4 * i) = *(uint32_t *)(0xC1000000 + 4 * i);
}
}
6.摄像头图片显示,这里切换图层。
/*!
\brief display image to lcd
\param[in] display_image_addr: image display address
\param[out] none
\retval none
*/
void image_display(uint32_t display_image_addr)
{
tli_layer_parameter_struct tli_layer1_initstruct;
/* input address configuration */
tli_layer1_initstruct.layer_frame_bufaddr = (uint32_t)display_image_addr;
/* layer1 windowing configuration */
tli_layer1_initstruct.layer_window_leftpos = 162;
tli_layer1_initstruct.layer_window_rightpos = (160 + 240 - 1);
tli_layer1_initstruct.layer_window_toppos = 12;
tli_layer1_initstruct.layer_window_bottompos = (12 + 272 - 1);
/* pixel format configuration */
tli_layer1_initstruct.layer_ppf = LAYER_PPF_RGB565;
/* alpha constant configuration : the constant alpha for layer 1 is decreased
to see the layer 0 in the intersection zone*/
tli_layer1_initstruct.layer_sa = 255;
/* default color configuration (configure A,R,G,B component values) */
tli_layer1_initstruct.layer_default_blue = 0xFF;
tli_layer1_initstruct.layer_default_green = 0xFF;
tli_layer1_initstruct.layer_default_red = 0xFF;
tli_layer1_initstruct.layer_default_alpha = 0;
/* blending factors */
tli_layer1_initstruct.layer_acf1 = LAYER_ACF1_PASA;
tli_layer1_initstruct.layer_acf2 = LAYER_ACF1_PASA;
// /* configure input address : frame buffer is located at memory */
// tli_layer1_initstruct.layer_frame_bufaddr = (uint32_t)0xC1000000;
tli_layer1_initstruct.layer_frame_line_length = ((240 * 2) + 3);
tli_layer1_initstruct.layer_frame_buf_stride_offset = (240 * 2);
tli_layer1_initstruct.layer_frame_total_line_number = 272;
tli_layer_init(LAYER1, &tli_layer1_initstruct);
tli_dither_config(TLI_DITHER_ENABLE);
tli_layer_init(LAYER1, &tli_layer1_initstruct);
/* disenable layer0 */
tli_layer_disable(LAYER0);
/* enable layer1 */
tli_layer_enable(LAYER1);
/* reload configuration */
tli_reload_config(TLI_REQUEST_RELOAD_EN);
/* enable TLI */
tli_enable();
}
7.在Project\gd32h7xx_it.c添加中断处理函数。EXTI10_15_IRQHandler是Tamper按键中断,保存照片数据和使能DMA1和打开摄像头DCI俘获.DMA1_Channel7_IRQHandler处理数据将320*240摄像头数据转为240*272屏显数据。
/*!
\brief this function handles external lines 10 to 15 interrupt request
\param[in] none
\param[out] none
\retval none
*/
void EXTI10_15_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_13))
{
printf("Key Pressed\r\n");
exti_interrupt_flag_clear(EXTI_13);
dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
dma_channel_enable(DMA1, DMA_CH7);
dci_capture_enable();
image_save();
image_display((uint32_t)image_background1);
delay_1ms(500);
image_display((uint32_t)0XC1000000);
//lv_demo_music();
}
}
/*!
\brief this function handles DMA1_Channel7_IRQ interrupt request
\param[in] none
\param[out] none
\retval none
*/
void DMA1_Channel7_IRQHandler(void)
{
/* 320*240 size image convert to 240*272 size image */
if(dma_interrupt_flag_get(DMA1, DMA_CH7, DMA_INT_FLAG_FTF))
{
//printf("\r\nprocess image\r\n");
int i = 0, x = 0, y = 0;
dma_channel_disable(DMA1, DMA_CH7);
for(x = 0; x < 320; x++) {
for(y = 0; y < 240; y++) {
if(x < 272) {
*(uint16_t *)(0xC1000000 + 2 * i) = *(uint16_t *)(0xC0000000 + 2 * ((320 * y) + x));
i++;
}
}
}
dma_interrupt_flag_clear(DMA1, DMA_CH7, DMA_INT_FLAG_FTF);
dma_channel_enable(DMA1, DMA_CH7);
}
}
8.main函数
int main(void)
{
ov2640_id_struct ov2640id;
BaseType_t ret;
/* enable the CPU cache */
cache_enable();
/* initialize the LEDs */
test_status_led_init();
/* configure systick */
systick_config();
/* flash the LEDs for 2 time */
led_flash(2);
/* configure USART0 */
usart_config();
/* configure TAMPER key */
gd_eval_key_init(KEY_TAMPER, KEY_MODE_EXTI);
/* output a message on hyperterminal using printf function */
printf("\r\n ================= LVGL ================= \r\n");
/**********************LCD Application**********************/
/* config the EXMC access mode */
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
printf("\r\nSDRAM Init \r\n");
/* camera initialization */
dci_ov2640_init();
dci_ov2640_id_read(&ov2640id);
printf("\r\nMF1:0x%02x,MF2:0x%02x,version:0x%02x,pid:0x%02x\r\n",ov2640id.manufacturer_id1,\
ov2640id.manufacturer_id2,ov2640id.version,ov2640id.pid);
nvic_configuration();
/* DMA interrupt and channel enable */
dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
dma_channel_enable(DMA1, DMA_CH7);
/* DCI enable */
dci_enable();
dci_capture_enable();
delay_1ms(100);
dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE);
dma_channel_disable(DMA1, DMA_CH7);
dci_capture_disable();
timer_config();
lcd_config();
lcd_init();
/* configure the GPIO of SPI touch panel */
touch_panel_gpio_configure();
#if (LVGL_DMA)
delay_1ms(50);
dma_config();
delay_1ms(1000);
#endif
lv_init();
lv_port_disp_init();
lv_port_indev_init();
// lv_demo_music();
lv_demo_widgets();
while(1)
{
delay_1ms(5);
lv_task_handler();
}
}
三.测验
编译烧录后,可以看到LCD显示widgets,Tamper按键按下后,屏创建了一个240*272的摄像头视频显示.效果见视频。成像效果感觉一般,优化可以调整ov2640_svga_init_reg_tbl寄存器参数。
至此,实现摄像头图像屏显。
camera_show