目录
一、图片显示部分
GIF
piclib.c介绍
图像显示实验main.c介绍
二、SD卡模块
1、SD卡基础知识
2、SD卡读操作
3、SD卡写操作
一、图片显示部分
GIF
- GIF(Graphics Interchange Format)的原义是“图像互换格式”,是CompuServe公司在1987年开发的图像文件格式。GIF文件的数据。是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右,它不属于任何应用程序。
- GIF主要分为两个版本,即GIF 89a和GIF 87a
GIF 87a:是在1987年制定的版本GIF
GIF 89a:是1989年制定的版本。
本质上是动态地显示多个连续的图片
piclib.c介绍
piclib.c中定义了LCD液晶显示需要使用的函数
#include "piclib.h"
#include "lcd.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK MiniSTM32开发板
//图片解码 驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2014/3/14
//版本:V2.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//升级说明
//V2.0
//1,将jpeg解码库换成了TJPGD,支持更多的jpg/jpeg文件,支持小尺寸图片快速jpeg显示
//2,pic_phy里面新增fillcolor函数,用于填充显示,以提高小尺寸jpg图片的显示速度
//3,ai_load_picfile函数,新增一个参数:fast,用于设置是否使能jpeg/jpg快速显示
//注意:这里的小尺寸是指:jpg/jpeg图片尺寸小于等于LCD尺寸.
//
_pic_info picinfo; //图片信息
_pic_phy pic_phy; //图片显示物理接口
//
//lcd.h没有提供划横线函数,需要自己实现
void piclib_draw_hline(u16 x0,u16 y0,u16 len,u16 color)
{
if((len==0)||(x0>lcddev.width)||(y0>lcddev.height))return;
LCD_Fill(x0,y0,x0+len-1,y0,color);
}
//填充颜色
//x,y:起始坐标
//width,height:宽度和高度。
//*color:颜色数组
void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color)
{
LCD_Color_Fill(x,y,x+width-1,y+height-1,color);
}
//
//画图初始化,在画图之前,必须先调用此函数
//指定画点/读点
void piclib_init(void)
{
pic_phy.read_point=LCD_ReadPoint; //读点函数实现
pic_phy.draw_point=LCD_Fast_DrawPoint; //画点函数实现
pic_phy.fill=LCD_Fill; //填充函数实现
pic_phy.draw_hline=piclib_draw_hline; //画线函数实现
pic_phy.fillcolor=piclib_fill_color; //颜色填充函数实现
picinfo.lcdwidth=lcddev.width; //得到LCD的宽度像素
picinfo.lcdheight=lcddev.height;//得到LCD的高度像素
picinfo.ImgWidth=0; //初始化宽度为0
picinfo.ImgHeight=0;//初始化高度为0
picinfo.Div_Fac=0; //初始化缩放系数为0
picinfo.S_Height=0; //初始化设定的高度为0
picinfo.S_Width=0; //初始化设定的宽度为0
picinfo.S_XOFF=0; //初始化x轴的偏移量为0
picinfo.S_YOFF=0; //初始化y轴的偏移量为0
picinfo.staticx=0; //初始化当前显示到的x坐标为0
picinfo.staticy=0; //初始化当前显示到的y坐标为0
}
//快速ALPHA BLENDING算法.
//src:源颜色
//dst:目标颜色
//alpha:透明程度(0~32)
//返回值:混合后的颜色.
u16 piclib_alpha_blend(u16 src,u16 dst,u8 alpha)
{
u32 src2;
u32 dst2;
//Convert to 32bit |-----GGGGGG-----RRRRR------BBBBB|
src2=((src<<16)|src)&0x07E0F81F;
dst2=((dst<<16)|dst)&0x07E0F81F;
//Perform blending R:G:B with alpha in range 0..32
//Note that the reason that alpha may not exceed 32 is that there are only
//5bits of space between each R:G:B value, any higher value will overflow
//into the next component and deliver ugly result.
dst2=((((dst2-src2)*alpha)>>5)+src2)&0x07E0F81F;
return (dst2>>16)|dst2;
}
//初始化智能画点
//内部调用
void ai_draw_init(void)
{
float temp,temp1;
temp=(float)picinfo.S_Width/picinfo.ImgWidth;
temp1=(float)picinfo.S_Height/picinfo.ImgHeight;
if(temp<temp1)temp1=temp;//取较小的那个
if(temp1>1)temp1=1;
//使图片处于所给区域的中间
picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2;
picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2;
temp1*=8192;//扩大8192倍
picinfo.Div_Fac=temp1;
picinfo.staticx=0xffff;
picinfo.staticy=0xffff;//放到一个不可能的值上面
}
//判断这个像素是否可以显示
//(x,y) :像素原始坐标
//chg :功能变量.
//返回值:0,不需要显示.1,需要显示
u8 is_element_ok(u16 x,u16 y,u8 chg)
{
if(x!=picinfo.staticx||y!=picinfo.staticy)
{
if(chg==1)
{
picinfo.staticx=x;
picinfo.staticy=y;
}
return 1;
}else return 0;
}
//智能画图
//FileName:要显示的图片文件 BMP/JPG/JPEG/GIF
//x,y,width,height:坐标及显示区域尺寸
//fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能.
//图片在开始和结束的坐标点范围内显示
u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast)
{
u8 res;//返回值
u8 temp;
if((x+width)>picinfo.lcdwidth)return PIC_WINDOW_ERR; //x坐标超范围了.
if((y+height)>picinfo.lcdheight)return PIC_WINDOW_ERR; //y坐标超范围了.
//得到显示方框大小
if(width==0||height==0)return PIC_WINDOW_ERR; //窗口设定错误
picinfo.S_Height=height;
picinfo.S_Width=width;
//显示区域无效
if(picinfo.S_Height==0||picinfo.S_Width==0)
{
picinfo.S_Height=lcddev.height;
picinfo.S_Width=lcddev.width;
return FALSE;
}
if(pic_phy.fillcolor==NULL)fast=0;//颜色填充函数未实现,不能快速显示
//显示的开始坐标点
picinfo.S_YOFF=y;
picinfo.S_XOFF=x;
//文件名传递
temp=f_typetell((u8*)filename); //得到文件的类型
switch(temp)
{
case T_BMP:
res=stdbmp_decode(filename); //解码bmp
break;
case T_JPG:
case T_JPEG:
res=jpg_decode(filename,fast); //解码JPG/JPEG
break;
case T_GIF:
res=gif_decode(filename,x,y,width,height); //解码gif
break;
default:
res=PIC_FORMAT_ERR; //非图片格式!!!
break;
}
return res;
}
_pic_phy是函数指针结构体
typedef struct
{
u16(*read_point)(u16,u16); //u16 read_point(u16 x,u16 y) 读点函数
void(*draw_point)(u16,u16,u16); //void draw_point(u16 x,u16 y,u16 color) 画点函数
void(*fill)(u16,u16,u16,u16,u16); ///void fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color) 单色填充函数
void(*draw_hline)(u16,u16,u16,u16); //void draw_hline(u16 x0,u16 y0,u16 len,u16 color) 画水平线函数
void(*fillcolor)(u16,u16,u16,u16,u16*); //void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color) 颜色填充
}_pic_phy;
ALPHA BLENDING算法是图像透明度处理算法
//快速ALPHA BLENDING算法.
//src:源颜色
//dst:目标颜色
//alpha:透明程度(0~32)
//返回值:混合后的颜色.
u16 piclib_alpha_blend(u16 src,u16 dst,u8 alpha)
{
u32 src2;
u32 dst2;
//Convert to 32bit |-----GGGGGG-----RRRRR------BBBBB|
src2=((src<<16)|src)&0x07E0F81F;
dst2=((dst<<16)|dst)&0x07E0F81F;
//Perform blending R:G:B with alpha in range 0..32
//Note that the reason that alpha may not exceed 32 is that there are only
//5bits of space between each R:G:B value, any higher value will overflow
//into the next component and deliver ugly result.
dst2=((((dst2-src2)*alpha)>>5)+src2)&0x07E0F81F;
return (dst2>>16)|dst2;
}
is_element_ok主要用于图像压缩显示部分
//判断这个像素是否可以显示
//(x,y) :像素原始坐标
//chg :功能变量.
//返回值:0,不需要显示.1,需要显示
u8 is_element_ok(u16 x,u16 y,u8 chg)
{
if(x!=picinfo.staticx||y!=picinfo.staticy)
{
if(chg==1)
{
picinfo.staticx=x;
picinfo.staticy=y;
}
return 1;
}else return 0;
}
智能画图函数ai_load_picfile,自动判断文件类型并将文件显示在你所指定的位置上
//智能画图
//FileName:要显示的图片文件 BMP/JPG/JPEG/GIF
//x,y,width,height:坐标及显示区域尺寸
//fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能.
//图片在开始和结束的坐标点范围内显示
u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast)
{
u8 res;//返回值
u8 temp;
if((x+width)>picinfo.lcdwidth)return PIC_WINDOW_ERR; //x坐标超范围了.
if((y+height)>picinfo.lcdheight)return PIC_WINDOW_ERR; //y坐标超范围了.
//得到显示方框大小
if(width==0||height==0)return PIC_WINDOW_ERR; //窗口设定错误
picinfo.S_Height=height;
picinfo.S_Width=width;
//显示区域无效
if(picinfo.S_Height==0||picinfo.S_Width==0)
{
picinfo.S_Height=lcddev.height;
picinfo.S_Width=lcddev.width;
return FALSE;
}
if(pic_phy.fillcolor==NULL)fast=0;//颜色填充函数未实现,不能快速显示
//显示的开始坐标点
picinfo.S_YOFF=y;
picinfo.S_XOFF=x;
//文件名传递
temp=f_typetell((u8*)filename); //得到文件的类型
switch(temp)
{
case T_BMP:
res=stdbmp_decode(filename); //解码bmp
break;
case T_JPG:
case T_JPEG:
res=jpg_decode(filename,fast); //解码JPG/JPEG
break;
case T_GIF:
res=gif_decode(filename,x,y,width,height); //解码gif
break;
default:
res=PIC_FORMAT_ERR; //非图片格式!!!
break;
}
return res;
}
图像显示实验main.c介绍
pic_get_tnum函数用来得到path路径下存储目标文件的个数,关键是使用了f_readdir函数。此函数来自于FATFS文件操作系统。
//得到path路径下,目标文件的总个数
//path:路径
//返回值:总有效文件数
u16 pic_get_tnum(u8 *path)
{
u8 res;
u16 rval=0;
DIR tdir; //临时目录
FILINFO tfileinfo; //临时文件信息
u8 *fn;
res=f_opendir(&tdir,(const TCHAR*)path); //打开目录
tfileinfo.lfsize=_MAX_LFN*2+1; //长文件名最大长度
tfileinfo.lfname=mymalloc(tfileinfo.lfsize);//为长文件缓存区分配内存
if(res==FR_OK&&tfileinfo.lfname!=NULL)
{
while(1)//查询总的有效文件数
{
res=f_readdir(&tdir,&tfileinfo); //读取目录下的一个文件
if(res!=FR_OK||tfileinfo.fname[0]==0)break; //错误了/到末尾了,退出
fn=(u8*)(*tfileinfo.lfname?tfileinfo.lfname:tfileinfo.fname);
res=f_typetell(fn);
if((res&0XF0)==0X50)//取高四位,看看是不是图片文件
{
rval++;//有效文件数增加1
}
}
}
return rval;
}
dir_sdi函数用来改变当前目录的索引,同样也是来源于ff.c文件
while(res==FR_OK)//打开成功
{
dir_sdi(&picdir,picindextbl[curindex]); //改变当前目录索引
res=f_readdir(&picdir,&picfileinfo); //读取目录下的一个文件
if(res!=FR_OK||picfileinfo.fname[0]==0)break; //错误了/到末尾了,退出
fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);
strcpy((char*)pname,"0:/PICTURE/"); //复制路径(目录)
strcat((char*)pname,(const char*)fn); //将文件名接在后面
LCD_Clear(BLACK);
ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片
Show_Str(2,2,240,16,pname,16,1); //显示图片名字
t=0;
while(1)
{
key=KEY_Scan(0); //扫描按键
if(t>250)key=1; //模拟一次按下KEY0
if((t%20)==0)LED0=!LED0;//LED0闪烁,提示程序正在运行.
if(key==KEY1_PRES) //上一张
{
if(curindex)curindex--;
else curindex=totpicnum-1;
break;
}else if(key==KEY0_PRES)//下一张
{
curindex++;
if(curindex>=totpicnum)curindex=0;//到末尾的时候,自动从头开始
break;
}else if(key==WKUP_PRES)
{
pause=!pause;
LED1=!pause; //暂停的时候LED1亮.
}
if(pause==0)t++;
delay_ms(10);
}
res=0;
}
二、SD卡模块
1、SD卡基础知识
SD卡 (Secure Digital Memory Card)即:安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。
SD卡按容量分类,可以分为3类:SD卡、SDHC卡、SDXC卡,如下表所示:
容量 | 命名 | 简称 |
0~2G | Standard Capacity SD Memory Card | SDSC或SD |
2G~32G | High Capacity SD Memory Card | SDHC |
32G~2T | Extended Capacity SD Memory Card | SDXC |
①初始化SPI接口及相关IO。
通过SPI连接SD卡,所以先要初始化MCU的SPI接口,以及相关IO。
②上电延时(>74个CLK)。
③卡复位(CMD0),进入IDLE状态。
发送CMD0时,CS必须为低电平,使得SD卡进入SPI模式。
④发送CMD8,检查是否支持SD卡2.0协议。
⑤根据不同协议检查SD卡(相关命令:CMD55、CMD41、CMD58和CMD1等)。
⑥取消片选,发多8个CLK,结束初始化 。
下图来自《SD卡2.0协议.pdf》这个文档。
下图是MiniSTM插入卡座
正点原子提供了SD卡驱动代码
SD_Select函数主要用来选定SD卡
//选择sd卡,并且等待卡准备OK
//返回值:0,成功;1,失败;
u8 SD_Select(void)
{
SD_CS=0;
if(SD_WaitReady()==0)return 0;//等待成功
SD_DisSelect();
return 1;//等待失败
}
SD_SendCmd命令用来向SD卡发送命令,cmd——8位,arg——32位,crc——8位。
//向SD卡发送一个命令
//输入: u8 cmd 命令
// u32 arg 命令参数
// u8 crc crc校验值
//返回值:SD卡返回的响应
u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry=0;
SD_DisSelect();//取消上次片选
if(SD_Select())return 0XFF;//片选失效
//发送
SD_SPI_ReadWriteByte(cmd | 0x40);//分别写入命令
SD_SPI_ReadWriteByte(arg >> 24);
SD_SPI_ReadWriteByte(arg >> 16);
SD_SPI_ReadWriteByte(arg >> 8);
SD_SPI_ReadWriteByte(arg);
SD_SPI_ReadWriteByte(crc);
if(cmd==CMD12)SD_SPI_ReadWriteByte(0xff);//Skip a stuff byte when stop reading
//等待响应,或超时退出
Retry=0X1F;
do
{
r1=SD_SPI_ReadWriteByte(0xFF);
}while((r1&0X80) && Retry--);
//返回状态值
return r1;
}
SD卡的类型也被宏定义后放在了 MMC_SD.h头文件下面
其中SD_TYPE_ERR 表示SD卡类型无效,值为0
// SD卡类型定义
#define SD_TYPE_ERR 0X00
#define SD_TYPE_MMC 0X01
#define SD_TYPE_V1 0X02
#define SD_TYPE_V2 0X04
#define SD_TYPE_V2HC 0X06
SD卡初始化的时候需要低速模式,正常工作是高速模式。可以设置为4分频。
//SD卡初始化的时候,需要低速
void SD_SPI_SpeedLow(void)
{
SPI1_SetSpeed(SPI_BaudRatePrescaler_256);//设置到低速模式
}
//SD卡正常工作的时候,可以高速了
void SD_SPI_SpeedHigh(void)
{
SPI1_SetSpeed(SPI_BaudRatePrescaler_2);//设置到高速模式
}
2、SD卡读操作
SD_ReadDisk用来读取数据,cnt=1表示只读取一个扇区,否则是连续读取。
//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
if(cnt==1)
{
r1=SD_SendCmd(CMD17,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SD_RecvData(buf,512);//接收512个字节
}
}else
{
r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
do
{
r1=SD_RecvData(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
SD_SendCmd(CMD12,0,0X01); //发送停止命令
}
SD_DisSelect();//取消片选
return r1;//
}
if是单块数据块的读取,else是多块的读取。
if(cnt==1)
else
{
}
多块数据读取的最后我们要加上发送CMD12指令,结束数据块的读取。
SD_SendCmd(CMD12,0,0X01); //发送停止命令
SD_GetResponse 用来等待SD卡返回想要的值,1表示返回成功,0表示失败。
//等待SD卡回应
//Response:要得到的回应值
//返回值:0,成功得到了该回应值
// 其他,得到回应值失败
u8 SD_GetResponse(u8 Response)
{
u16 Count=0xFFFF;//等待次数
while ((SD_SPI_ReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应
if (Count==0)return MSD_RESPONSE_FAILURE;//得到回应失败
else return MSD_RESPONSE_NO_ERROR;//正确回应
}
3、SD卡写操作
SD_WriteDisk是为了写入数据,同样也要先判断SD_Type的类型。
//写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址
if(cnt==1)
{
r1=SD_SendCmd(CMD24,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SD_SendBlock(buf,0xFE);//写512个字节
}
}else
{
if(SD_Type!=SD_TYPE_MMC)
{
SD_SendCmd(CMD55,0,0X01);
SD_SendCmd(CMD23,cnt,0X01);//发送指令
}
r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令
if(r1==0)
{
do
{
r1=SD_SendBlock(buf,0xFC);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
r1=SD_SendBlock(0,0xFD);//接收512个字节
}
}
SD_DisSelect();//取消片选
return r1;//
}
SD_GetSectorCount可以得到SD卡的总扇区数量,对于V2.0版本以后的SD卡是固定512Byte(即一个sector)大小。
//获取SD卡的总扇区数(扇区数)
//返回值:0: 取容量出错
// 其他:SD卡的容量(扇区数/512字节)
//每扇区的字节数必为512,因为如果不是512,则初始化不能通过.
u32 SD_GetSectorCount(void)
{
u8 csd[16];
u32 Capacity;
u8 n;
u16 csize;
//取CSD信息,如果期间出错,返回0
if(SD_GetCSD(csd)!=0) return 0;
//如果为SDHC卡,按照下面方式计算
if((csd[0]&0xC0)==0x40) //V2.00的卡
{
csize = csd[9] + ((u16)csd[8] << 8) + 1;
Capacity = (u32)csize << 10;//得到扇区数
}else//V1.XX的卡
{
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
Capacity= (u32)csize << (n - 9);//得到扇区数
}
return Capacity;
}