【STM32调试(三)】采集bmp图像保存在SD卡

将图像保存在SD卡

一、思路

这里保存的是BMP图像,需要先连接bmp图像的数据格式。在STM32上采集的数据格式是RGB565方便在LCD上显示。如果直接发送还需要处理RGB565到RGB555的格式转换,以及bmp的数据头信息。

将bmp保存在sd卡上,这里移植FATFS文件系统。

图片保存的步骤:

  • 配置bmp的图片头信息,
  • 设置数据格式掩码,
  • 写入图像数据。

二、移植文件系统

我们使用FATFS文件系统来管理SD卡,

  1. FATFS文件系统

FATFS文件系统也就是一个软件,直接去官网下载最新版本即可。官网也有基本的介绍。下图是文件系统的结构:

在这里插入图片描述

  • 应用层:

FATFS提供文件操作的API给应用层使用,完成自己的应用开发,不再需要考虑底层硬件的操作。这也是FATFS移植性好的原因。

  • 中间层:

就是FATFS文件系统连接底层和应用层的桥梁,调用底层驱动封装成文件操作的API给应用层使用。这部分不需要修改,只需要简单的修改配置文件即可。
修改共10处,修改如下(正点原子):具体看注释

ffconf.h 
//line13
#define _FS_TINY        0   /* 0:Normal or 1:Tiny */
//line20
#define _FS_READONLY    0   /* 0:Read/Write or 1:Read only */
//line36
#define _USE_STRFUNC    1   /* 0:Disable or 1-2:Enable */
//line40
#define _USE_MKFS       1   /* 0:Disable or 1:Enable */
//line44
#define _USE_FASTSEEK   1   /* 0:Disable or 1:Enable */
//line48
#define _USE_LABEL      1   /* 0:Disable or 1:Enable */
//line60
#define _CODE_PAGE  936     //采用中文GBK编码
//line92
#define _USE_LFN    3       /* 0 to 3 设置为1,支持长文件名,并采用动态内存*/
//line135
#define _VOLUMES    2
//line155
#define _MAX_SS     512

  • 底层:

底层是移植文件系统时需要我们自己根据自己的硬件平台进行编写diskio.c。我直接使用正点原子提供的代码,但是自己不写也要读懂理解啊。
这里有6个函数:

DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
//还有一个 get_fattime 函数,只读配置可以不用写

/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs     (C)ChaN, 2013        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control module to the FatFs module with a defined API.        */
/*-----------------------------------------------------------------------*/

#include "diskio.h"     /* FatFs lower layer API */
#include "mmc_sd.h"
#include "flash.h"
#include "malloc.h"     
//   
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//FATFS disio.c 驱动代码       
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2014/3/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved                                     
// 

#define SD_CARD  0  //SD卡,卷标为0
#define EX_FLASH 1  //外部flash,卷标为1

#define FLASH_SECTOR_SIZE   512           
//对于W25Q64 
//前4.8M字节给fatfs用,4.8M字节后~4.8M+100K给用户用,4.9M以后,用于存放字库,字库占用3.09M.                     
u16     FLASH_SECTOR_COUNT= 9832;   //4.8M字节,默认为W25Q64
#define FLASH_BLOCK_SIZE    8       //每个BLOCK有8个扇区

//初始化磁盘
DSTATUS disk_initialize (
    BYTE pdrv               /* Physical drive nmuber (0..) */
)
{
    u8 res=0;       
    switch(pdrv)
    {
        case SD_CARD://SD卡
            res = SD_Initialize();//SD_Initialize() 
            if(res)//STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
            {
                SD_SPI_SpeedLow();
                SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
                SD_SPI_SpeedHigh();
            }
            break;
        case EX_FLASH://外部flash
            SPI_Flash_Init();
            if(SPI_FLASH_TYPE==W25Q64)FLASH_SECTOR_COUNT=9832;  //W25Q64
            else FLASH_SECTOR_COUNT=0;                          //其他
            break;
        default:
            res=1; 
    }        
    if(res)return  STA_NOINIT;
    else return 0; //初始化成功
}  

//获得磁盘状态
DSTATUS disk_status (
    BYTE pdrv       /* Physical drive nmuber (0..) */
)
{ 
    return 0;
} 

//读扇区
//drv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
DRESULT disk_read (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE *buff,     /* Data buffer to store read data */
    DWORD sector,   /* Sector address (LBA) */
    UINT count      /* Number of sectors to read (1..128) */
)
{
    u8 res=0; 
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误           
    switch(pdrv)
    {
        case SD_CARD://SD卡
            res=SD_ReadDisk(buff,sector,count);  
            if(res)//STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
            {
                SD_SPI_SpeedLow();
                SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
                SD_SPI_SpeedHigh();
            }
            break;
        case EX_FLASH://外部flash
            for(;count>0;count--)
            {
                SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
                sector++;
                buff+=FLASH_SECTOR_SIZE;
            }
            res=0;
            break;
        default:
            res=1; 
    }
   //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
    if(res==0x00)return RES_OK;  
    else return RES_ERROR;     
}

//写扇区
//drv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _USE_WRITE
DRESULT disk_write (
    BYTE pdrv,          /* Physical drive nmuber (0..) */
    const BYTE *buff,   /* Data to be written */
    DWORD sector,       /* Sector address (LBA) */
    UINT count          /* Number of sectors to write (1..128) */
)
{
    u8 res=0;  
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误           
    switch(pdrv)
    {
        case SD_CARD://SD卡
            res=SD_WriteDisk((u8*)buff,sector,count);
            break;
        case EX_FLASH://外部flash
            for(;count>0;count--)
            {                                           
                SPI_Flash_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
                sector++;
                buff+=FLASH_SECTOR_SIZE;
            }
            res=0;
            break;
        default:
            res=1; 
    }
    //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
    if(res == 0x00)return RES_OK;    
    else return RES_ERROR;  
}
#endif

//其他表参数的获得
 //drv:磁盘编号0~9
 //ctrl:控制代码
 //*buff:发送/接收缓冲区指针
#if _USE_IOCTL
DRESULT disk_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
{
    DRESULT res;                                         
    if(pdrv==SD_CARD)//SD卡
    {
        switch(cmd)
        {
            case CTRL_SYNC:
                SD_CS=0;
                if(SD_WaitReady()==0)res = RES_OK; 
                else res = RES_ERROR;     
                SD_CS=1;
                break;   
            case GET_SECTOR_SIZE:
                *(WORD*)buff = 512;
                res = RES_OK;
                break;   
            case GET_BLOCK_SIZE:
                *(WORD*)buff = 8;
                res = RES_OK;
                break;   
            case GET_SECTOR_COUNT:
                *(DWORD*)buff = SD_GetSectorCount();
                res = RES_OK;
                break;
            default:
                res = RES_PARERR;
                break;
        }
    }else if(pdrv==EX_FLASH)    //外部FLASH  
    {
        switch(cmd)
        {
            case CTRL_SYNC:
                res = RES_OK; 
                break;   
            case GET_SECTOR_SIZE:
                *(WORD*)buff = FLASH_SECTOR_SIZE;
                res = RES_OK;
                break;   
            case GET_BLOCK_SIZE:
                *(WORD*)buff = FLASH_BLOCK_SIZE;
                res = RES_OK;
                break;   
            case GET_SECTOR_COUNT:
                *(DWORD*)buff = FLASH_SECTOR_COUNT;
                res = RES_OK;
                break;
            default:
                res = RES_PARERR;
                break;
        }
    }else res=RES_ERROR;//其他的不支持
    return res;
}
#endif
//获得时间
//User defined function to give a current time to fatfs module      */
//31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */                                                                                                                                                                                                                                          
//15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */                                                                                                                                                                                                                                                
DWORD get_fattime (void)
{                
    return 0;
}            
//动态分配内存
void *ff_memalloc (UINT size)           
{
    return (void*)mymalloc(size);
}
//释放内存
void ff_memfree (void* mf)       
{
    myfree(mf);
}

  1. 移植

引用c文件和头文件即可

  1. 使用
mem_init(); //初始化内存池

exfuns_init(); //为 fatfs 相关变量申请内存
f_mount(fs[0],"0:",1); //挂载 SD 卡
f_mount(fs[1],"1:",1); //挂载 FLASH.
//使用接口函数:f_open、f_read、etc.

三、保存图片

bmp头结构:
1、文件头的结构定义了该图片的框架信息,占14个字节:

(1)bfType:指定文件类型,必须是 0x424D,即字符串”BM”,也就是说.bmp文件的关键字都是“BM”。
(2)bfSize:指定文件大小。
(3)bfOffBits:实际数据占文件头的偏移量。

2、信息头的结构定义了该图片的具体信息,占40个字节:

(1)biWidth:指定图像宽度,单位:像素
(2)biHeight:指定图像高度,单位:像素
(3)biBitCount:指定颜色位数,常用的值为1(灰度图),4(16色图),8(256色图),24(真彩色图),32(真彩色图,增加ALPHA通道)

结构体:参考文章

//BMP头文件
typedef __packed struct
{
    u16  bfType ;     //文件标志.只对'BM',用来识别BMP位图类型
    u32  bfSize ;     //文件大小,占四个字节
    u16  bfReserved1 ;//保留
    u16  bfReserved2 ;//保留
    u32  bfOffBits ;  //从文件开始到位图数据(bitmap data)开始之间的的偏移量
}BITMAPFILEHEADER ;
//BMP信息头
typedef __packed struct
{
    u32 biSize ;                //说明BITMAPINFOHEADER结构所需要的字数。
    long  biWidth ;         //说明图象的宽度,以象素为单位 
    long  biHeight ;        //说明图象的高度,以象素为单位 
    u16  biPlanes ;         //为目标设备说明位面数,其值将总是被设为1 
    u16  biBitCount ;       //说明比特数/象素,其值为1、4、8、16、24、或32
/*说明图象数据压缩的类型。其值可以是下述值之一:
BI_RGB:没有压缩;
BI_RLE8:每个象素8比特的RLE压缩编码,压缩格式由2字节组成(重复象素计数和颜色索引);  
BI_RLE4:每个象素4比特的RLE压缩编码,压缩格式由2字节组成
BI_BITFIELDS:每个象素的比特由指定的掩码决定。*/
    u32 biCompression ;     
    u32 biSizeImage ;       //说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0  
    long  biXPelsPerMeter ; //说明水平分辨率,用象素/米表示
    long  biYPelsPerMeter ; //说明垂直分辨率,用象素/米表示
    u32 biClrUsed ;         //说明位图实际使用的彩色表中的颜色索引数
    u32 biClrImportant ;    //说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。 
}BITMAPINFOHEADER ;
//彩色表 
typedef __packed struct 
{
    u8 rgbBlue ;    //指定蓝色强度
    u8 rgbGreen ;   //指定绿色强度 
    u8 rgbRed ;     //指定红色强度 
    u8 rgbReserved ;//保留,设置为0 
}RGBQUAD ;
//整体信息头
typedef __packed struct
{ 
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;  
    RGBQUAD RGB_MASK[3];            //调色板用于存放RGB掩码.
}BITMAPINFO; 

写bmp头:

//打开文件,若不存在就创建
res_sd = f_open(&fnew, "0:test1.bmp", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);

//文件打开成功
if(res_sd == FR_OK)
{
    //填写文件信息头信息  
    bmp.bmfHeader.bfType = 0x4D42;          //bmp类型  "BM"
    bmp.bmfHeader.bfSize= 54 + 320*240*2;   //文件大小(信息结构体+像素数据)
    bmp.bmfHeader.bfReserved1 = 0x0000;     //保留,必须为0
    bmp.bmfHeader.bfReserved2 = 0x0000;             
    bmp.bmfHeader.bfOffBits=54;             //位图信息结构体所占的字节数

    //填写位图信息头信息  
    bmp.bmiHeader.biSize=40;            //位图信息头的大小
    bmp.bmiHeader.biWidth=320;          //位图的宽度
    bmp.bmiHeader.biHeight=240;         //图像的高度
    bmp.bmiHeader.biPlanes=1;           //目标设别的级别,必须是1
    bmp.bmiHeader.biBitCount=16;        //每像素位数
    bmp.bmiHeader.biCompression=3;      //RGB555格式
    bmp.bmiHeader.biSizeImage=320*240*2;//实际位图所占用的字节数(仅考虑位图像素数据)
    bmp.bmiHeader.biXPelsPerMeter=0;    //水平分辨率
    bmp.bmiHeader.biYPelsPerMeter=0;    //垂直分辨率
    bmp.bmiHeader.biClrImportant=0;     //说明图像显示有重要影响的颜色索引数目,0代表所有的颜色一样重要
    bmp.bmiHeader.biClrUsed=0;          //位图实际使用的彩色表中的颜色索引数,0表示使用所有的调色板项

    //RGB565格式掩码
    bmp.RGB_MASK[0].rgbBlue = 0;
    bmp.RGB_MASK[0].rgbGreen = 0xF8;
    bmp.RGB_MASK[0].rgbRed = 0;
    bmp.RGB_MASK[0].rgbReserved = 0;

    bmp.RGB_MASK[1].rgbBlue = 0xE0;
    bmp.RGB_MASK[1].rgbGreen = 0x07;
    bmp.RGB_MASK[1].rgbRed = 0;
    bmp.RGB_MASK[1].rgbReserved = 0;

    bmp.RGB_MASK[2].rgbBlue = 0x1F;
    bmp.RGB_MASK[2].rgbGreen = 0;
    bmp.RGB_MASK[2].rgbRed = 0;
    bmp.RGB_MASK[2].rgbReserved = 0;

    //写文件头进文件  
    res_sd= f_write(&fnew, &bmp, sizeof(bmp), &fnum);
}

写图像数据:

for(i=0;i<240;i++)
{
    for(j=0;j<320;j++)
    {               
        GPIOB->CRL=0X88888888;
        OV7725_RCK=0;
        color=OV7725_DATA;  //读数据--高8位

        OV7725_RCK=1; 
        color<<=8;  
        OV7725_RCK=0;
        color|=OV7725_DATA; //读数据   --低8位       (高低8+8位合并成一个u16发送)                              
        OV7725_RCK=1;
        GPIOB->CRL=0X33333333;
        //LCD显示
        LCD_WR_DATA(color);
        //写位图信息头进内存卡
        f_write(&fnew, &color, sizeof(color), &fnum);
    }
}

四、实验结果

效果挺好的。


在这里插入图片描述

先存后发,有效减少干扰。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339