野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 21528|回复: 2

ov2640_拍照并保存至sd卡_图片花屏

[复制链接]
发表于 2017-4-8 15:47:06 | 显示全部楼层 |阅读模式
20火花
功能:          使用ov2640摄像头,将拍到的图片以rgb888格式保存至sd卡中
基本思路:

          1. 修改系统代码,将堆大小设置为8M;
          2. 修改分散脚本,使堆的地址为0xD0000000(sdram起始地址);
          2. 动态分配内存,并进行系列初始化,dma传输的目标的地址为动态创建内存的首地址;
          3. 使能dcmi并使能dcmi捕捉,延时1s后,关闭dcmi、dcmi捕捉和dma传输;
          4. 调用函数,将图像转换为rgb888;
          5. 使用文件系统,将rgb888写入sd卡;
所遇问题:
          在上位机中查看rgb888.bmp,最下方约30行显示正常,其余上部分全部花屏,参见图片
                                  test.jpg    此处是正常的,以上均为花屏

请留意:
          sdram已经过测试,堆的使用没有任何问题,最大可以分配 (0x800000-0xC) byte,略小于8M

核心代码:
bmp_rgb565_to_rgb888:
tmp_ptr = new_data_ptr + HEAD_INFO_SIZE;
for(i = 0; i < img_height; i++)
{
     data_ptr = data_addr + i*img_width;
     for(j = 0; j < img_width; j++)
     {
          *tmp_ptr++ = (data_ptr[j] & 0x001F) << 3;
          *tmp_ptr++ = ((data_ptr[j] & 0x07E0) >> 5) << 2;
          *tmp_ptr++ = ((data_ptr[j] & 0xF800) >> 11) << 3;
     }
}

上述转换在上位机上使用是没有任何问题的

中断服务程序:
void DMA2_Stream1_IRQHandler(void)
{
    if(SET == DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1))
    {
        current_height++;
        total_height++;

        if(current_height == g_img_height)
        {
            current_height = 0;
        }

        tmp = (uint32_t)g_dma_target_addr +
            g_img_width*2*(g_img_height-current_height-1);
        OV2640_DMA_Config(tmp, g_dma_buf_size);
        DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
    }
}

void DCMI_IRQHandler(void)
{
    if(SET == DCMI_GetITStatus(DCMI_IT_FRAME))
    {
        current_height = 0;
        flag++;
        DCMI->ICR = DCMI_IT_FRAME;
        //DCMI_ClearITPendingBit(DCMI_IT_FRAME);
    }
}


主函数:
int main(void)
{
    BL8782_PDN_INIT();
    OV2640_HW_Init();

    OV2640_IDTypeDef ov2640_id;
    OV2640_ReadID(&ov2640_id);

    if(ov2640_id.PIDH != 0x26)
    {
        DISP_ERR("not detected camera, please check the connection again \n");
        return -1;
    }

    DISP("Manufacturer_ID1 = %x \nManufacturer_ID2 = %x \n",
        ov2640_id.Manufacturer_ID1, ov2640_id.Manufacturer_ID2);

    sdram_bulk_reset();
    uint16_t *initail_data_ptr = (uint16_t *)malloc(g_img_size);
    if(!initail_data_ptr)
    {
        DISP_ERR(ERR_MALLOC);
        return -1;
    }
    g_dma_target_addr = initail_data_ptr;

    OV2640_Init();
    OV2640_UXGAConfig();

    DCMI_Cmd(ENABLE);
    DCMI_CaptureCmd(ENABLE);

    sleep(1);
    DISP("flag = %d \ncurrent_height = %d \ntotal_height = %d \n",
        flag, current_height, total_height);
    DCMI_Cmd(DISABLE);
    DCMI_CaptureCmd(DISABLE);
    DMA_Cmd(DMA2_Stream1, DISABLE);
    DISP("success disable \n");

    uint8_t *rgb888_ptr = NULL;
    rgb888_ptr = bmp_rgb565_to_rgb888(g_img_width, g_img_height, g_dma_target_addr);
    if(!rgb888_ptr)
    {
        DISP_ERR("error in bmp_rgb565_to_rgb888 \n");
        free(g_dma_target_addr);
        return -1;
    }

    uint32_t fnum = 0;
    res_sd = f_mount(&fs,"0:",1);
    if(res_sd == FR_NO_FILESYSTEM)
    {
        DISP("no file system in sd card, ready to format \n");
        res_sd=f_mkfs("0:",0,0);
        if(res_sd == FR_OK)
        {
            DISP("success formatting\n");
            res_sd = f_mount(NULL,"0:",1);
            res_sd = f_mount(&fs,"0:",1);
        }
        else
        {
            DISP("failed to format \n");
            return -1;
        }
    }
    else if(res_sd != FR_OK)
    {
        DISP("failed to mount, error code: %d \n", res_sd);
        DISP("ensure card inserted to the board \n");
        DISP("ensure 3V3_WIFI connetted to 3V3 instead of GND \n");
        return -1;
    }
    else
    {
        DISP("success mounting\n");
    }

    DISP("ready to write the file to card... \n");
    uint32_t real_width = COUNT_REAL_WIDTH(g_img_width*24);
    uint32_t real_size = real_width * g_img_height;
        res_sd = f_open(&fnew, "0:test.bmp", FA_CREATE_ALWAYS | FA_WRITE );
        if (res_sd == FR_OK)
        {
                DISP("success creating bmp file in sd card \n");
                res_sd = f_write(&fnew, rgb888_ptr, real_size+HEAD_INFO_SIZE, &fnum);
        if((res_sd == FR_OK) && (fnum == real_size+HEAD_INFO_SIZE))
        {
            DISP("success writing data to bmp file \n");
        }
        else
        {
            DISP("error in f_write:%d \n", res_sd);
        }

        f_close(&fnew);
        }
        else
        {       
                DISP_ERR("error in fopen");
        }
    f_mount(NULL,"0:",1);

    free(rgb888_ptr);
    free(g_dma_target_addr);
    DISP("end of project \n");

    return 0;
}


回复

使用道具 举报

 楼主| 发表于 2017-4-9 15:10:08 | 显示全部楼层
问题根源:
g_img_size 在外部定义为32位无符号整形,而在主函数的源文件中,声明为无符号16位整形,即:
in bsp_ov2640.c :
const uint32_t g_img_size = (g_img_height*g_img_width*2);

in main.c :
extern const uint16_t g_img_size;

关于malloc的知识:
本例中,堆的起始地址设置为sdram的起始地址,为0xD0000000,并设置为8字节对齐
假设一次分配size字节大小的内存,实际会如何分配?
主要分为2部分,内存大小(4字节)+ 数据(size字节)

以size = 800*480*2 = 0xBB800为例:

0xBB808,会被拆分为08 B8 0B,加上保留的四字节,
0xD0000000 - 0xD0000007的内容为:
00 00 00 00 08 B8 0B 00 (前四字节保留是为了8字节对齐)
0xD0000008 - 0xD00BB807为数据,所以malloc返回的地址为0xD0000008

对于容量仅有8MiB的sdram,可使用地址为0xD0000000 - 0xD07FFFFF,
在8字节对齐的情况下,只能使用0xD0000008 - 0xD07FFFFB,末尾4字节保留
故对于malloc而言,一次性最多只能分配“堆大小 - 12 ”字节的空间。

紧接前例,若再次分配空间,则malloc返回的地址是多少?
答案为 0xD00BB810
原因:
上次分配的0xBB800字节内存,实际占用0xD0000000 - 0xD00BB807,
0xD00BB808 - 0xD00BB80F,此8字节的前4字节保留(为了8字节对齐),后4字节存储内存大小,
所以从0xD00BB810开始分配内存,故malloc返回0xD00BB810,

查错思路:
keil单步调试,发现第一次malloc分配的地址和第二次分配的重合,以本例说明:

malloc第一次分配800*480*2字节的空间,起始地址为0xD0000008

malloc第二次分配800*480*3字节的空间,起始地址应为0xD00BB810
但在调试中发现,起始0xD00008010,丢失了BB两位数据

于是查看第一次分配的大小,g_img_size值显示为0xBB800,无异议,
那么导致malloc分配的地址重合,必然是因为访问内存越界。

经反复查询,无任何访问越界,开始怀疑malloc本身的bug。
于是对malloc进行测试,结果都正常。

在后面的调试中,将原先的malloc(g_img_size)换为malloc(800*480*2),
发现恢复了正常,那么,有理由怀疑,g_img_size的值并不等于800*480*2,
加上了一句至串口的语句,果然,g_img_size = 0xB800,丢失了两位数据,
经检查,发现是在声明外部变量时,写错了,即
in bsp_ov2640.c :
const uint32_t g_img_size = (g_img_height*g_img_width*2);

in main.c :
extern const uint16_t g_img_size;

这样就会导致一个问题:
当g_img_size 大于0xFFFF 时,高位数据就会丢失。
而此处定义的图片尺寸为800*480, 而800*480*2  = 0xBB800,
高位数据丢失后, g_img_size 在main.c中的值为0xB800,
当以此值分配内存时,实际只分配了0xB800字节,而下次分配内存,便会从0xB810开始,
但在dma传输数据时,实际是从0xD0000008 传输到 0xD00BB808,
当下次从0xB810开始读写数据时,0xB810 - 0xD00BB808间的图像数据就会被覆盖,导致图像丢失,
只有0xd0000008 - 0xd000B808间的数据是正常的,即仅有0xB800字节数据是正常的,
而0xB800约等于29行数据,进而表现为图片下方大约29行数据是正确的,而其余是错误的。

本人曾查过g_img_size的值,但一开始并没有往这个方向查错,归根于:
在线调试时,用keil查看g_img_size的值,显示为0xBB800,而实际已经丢失了高位数据

出错原因总结:
代码编写不够谨慎,微小的错误,导致大的异常;
keil误导,使得难以发现这个错

keil在线调试中,将光标移到一个变量上查看值,是一个很危险的行为。
对于外部的全局变量,此行为显示的是外部的全局变量,并非显示的当前文件中该变量的值。
对于外部的全局变量,若当前文件声明有差异,此行是无法察觉的,因为始终显示的是外部的正确值。

上述内容若有错误,欢迎纠正,谢谢
回复

使用道具 举报

发表于 2017-4-10 11:41:02 | 显示全部楼层
lvsenlv 发表于 2017-4-9 15:10
问题根源:
g_img_size 在外部定义为32位无符号整形,而在主函数的源文件中,声明为无符号16位整形,即:
...

调试的时候是要暂停再查看变量值的
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-4-25 23:12 , Processed in 0.047512 second(s), 26 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表