将图像保存在SD卡
一、思路
这里保存的是BMP图像,需要先连接bmp图像的数据格式。在STM32上采集的数据格式是RGB565方便在LCD上显示。如果直接发送还需要处理RGB565到RGB555的格式转换,以及bmp的数据头信息。
将bmp保存在sd卡上,这里移植FATFS文件系统。
图片保存的步骤:
- 配置bmp的图片头信息,
- 设置数据格式掩码,
- 写入图像数据。
二、移植文件系统
我们使用FATFS文件系统来管理SD卡,
- 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);
}
- 移植
引用c文件和头文件即可
- 使用
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);
}
}
四、实验结果
效果挺好的。
先存后发,有效减少干扰。