STM32应用案例 基于STM32F103ZE开发的数码相册
2023-06-05 来源:elecfans
1.硬件平台
CPU:STM32F103ZE
屏幕:3.5寸TFTLCD屏
触控:电阻式触摸屏xpt2046
SD卡、外扩Sram
2.示例效果
SD卡检测和图片搜索
图片加载与显示
3.软件设计
3.1 遍历目录
遍历目录,搜索所有的bmp格式图片,以链表方式保存图片名,方便接下来图片切换。
typedef struct FILE_info
{
char file_name[100];
u16 number;//保存第几张图片
struct FILE_info *next;
struct FILE_info *pre;
}FILE_INFO;
FILE_INFO *bmp_head=NULL;
/*创建链表*/
FILE_INFO *List_CreateHead(FILE_INFO *head)
{
if(head!=NULL)return head;
head=malloc(sizeof(FILE_INFO));
memset(head,0,sizeof(FILE_INFO));
head->next=NULL;
head->pre=NULL;
return head;
}
/*添加节点*/
FILE_INFO *List_AddNode(FILE_INFO *head)
{
if(head==NULL)return NULL;//链表头不存在
FILE_INFO *phead=head;
while(phead->next!=NULL)
{
phead=phead->next;
}
FILE_INFO *new_node=malloc(sizeof(FILE_INFO));
memset(new_node,0,sizeof(FILE_INFO));
new_node->pre=phead;
phead->next=new_node;
new_node->next=NULL;
return new_node;
}
/*遍历目录*/
u8 SDCard_PrintDir(const TCHAR* path)
{
DIR dp;
u8 res;
u8 stat=0;
bmp_head=List_CreateHead(bmp_head);//创建链表头
res=f_opendir(&dp,path);
FILINFO file_info;
if(res)
{
printf('打开目录失败res=%drn',res);
free(bmp_head);//释放链表头
return 1;
}
FILE_INFO *temp=NULL;
while(1)
{
res=f_readdir(&dp,&file_info);
if(res!=FR_OK || file_info.fname[0]==0)break;
if(strstr(file_info.fname,'.bmp'))//查找bmp图片
{
temp=List_AddNode(bmp_head);//添加节点
if(temp==NULL)
{
stat=2;//动态分配空间失败
goto AA;
}
strcpy(temp->file_name,file_info.fname);//文件名
picture_count++;
temp->number=picture_count;//第几张图片
//printf('文件名:%srn',temp->file_name);
}
}
AA:
f_closedir(&dp);//关闭目录
return stat;
}
3.2 图片解析与显示
图片通过SD卡保存,SD卡采用SDIO驱动。由于STM32F103ZE主频只有72MHZ,为了提高刷新速度,将主频超频至128MHZ。再通过外扩SRAM建立屏幕缓冲区,借助DMA数据搬运,从而提升屏幕刷新效率。
超频处理后需要注意串口波特率计算和定时器工作频率
void STM32_Clock_Init(u8 PLL)
{
u8 temp;
RCC->CFGR&=0XFFFFFFFC; //修改时钟频率为内部8M
RCC->CR&=~0x01000000; //PLLOFF
RCC->CFGR&=~(0XF<<18); //清空原来的设置
RCC->CR|=1<<16;//开启HSE时钟
while(!(RCC->CR>>17));//等待外部时钟就绪
RCC->CFGR|=0x4<<8;//APB1时钟由系统时钟2分频
PLL-=2;//实际倍频数和填入参数差2,9倍频写入的数值为7
RCC->CFGR|=PLL<<18;//PLL时钟9倍频
RCC->CFGR|=1<<16; //HSE作为PLL时钟输入源
FLASH->ACR|=0x32; //FLASH 2个延时周期
RCC->CR|=1<<24;//PLL时钟使能
while(!(RCC->CR>>25));//等待PLL锁定
RCC->CFGR|=0x2<<0;//PLL输出作为系统时钟
while(1)
{
temp=(RCC->CFGR>>2)&0x3;
if(temp==0x2)break;
}
}
3.3 DMA配置
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
/******DMA_CH1从存储器到存储器************
**形参:u32 cpar -- 外设地址
** u32 cmar -- 存储器地址
**
**************************************/
void DMA_CH1_Init(void)
{
RCC->AHBENR|=1<<0;//dma1时钟使能
DMA1_Channel1->CCR|=1<<14;//存储器到存储器模式
DMA1_Channel1->CCR|=0x3<<12;//设置CH1优先级为最高
DMA1_Channel1->CCR|=0x1<<10;//存储器数据宽度16位
DMA1_Channel1->CCR|=0x1<<8;//外设数据宽度16位
DMA1_Channel1->CCR|=1<<7;//存储器地址增量
DMA1_Channel1->CCR&=~(1<<6);//外设地址不增量
DMA1_Channel1->CCR&=~(1<<5);//不执行循环操作
//DMA1_Channel1->CCR|=1<<5;//执行循环操作
DMA1_Channel1->CCR&=~(1<<4);//从外设读
}
/**********开启DMA1_CH1数据传输*************
***
***形参:u16 data_len -- DMA要传输数目
***********************************************/
void DMA_CH1_Start(u32 cpar,u32 cmar,u16 data_len)
{
DMA1_Channel1->CPAR=cpar;//外设地址
DMA1_Channel1->CMAR=cmar;//存储器地址
DMA1_Channel1->CCR&=~(1<<0);//关闭通道传输
DMA1_Channel1->CNDTR=data_len;//设置传输数量
DMA1_Channel1->CCR|=1<<0;//开启通道传输
}
3.4 图片解析与显示
本示例主要以BMP图片为例,其他格式图形需要移植第三方库才可实现。
由于我们常规BMP图片多为24位真彩色,即RGB888;而本次使用的LCD屏是16位真彩色RGB565,因而需要进行颜色格式转换。
/*颜色转换RGB888转RGB565*/
u16 RGB888_Transform_RGB565(u32 rgb)
{
u8 r,g,b;
u16 rgb565;
r=(rgb>>16)>>3;
g=(rgb>>8)>>2;
b=(rgb&0xff)>>3;
rgb565=(r<<11)|(g<<5)|(b<<0);
return rgb565;
}
u16 picture_count=0;//图片总数量
/*BMP图片显示*/
static u8 buff_rgb888[320*3];//用来保存读取到的原始数据
static u16 buff_rgb565[320*480];//用来保存转换完成的RGB565数据
u8 BMP_Display(const char *file,u16 number)
{
FIL fp;
FRESULT res;
UINT br;
u16 w,h;
char buff[100];
snprintf(buff,sizeof(buff),'0:/photo/%s',file);
res=f_open(&fp,buff,FA_READ);
if(res!=FR_OK)return 1;
BMP_HEADER bmphead;
BMP_INFO bmpinfo;
memset(&bmphead,0,sizeof(BMP_HEADER));
memset(&bmpinfo,0,sizeof(BMP_INFO));
res=f_read(&fp,&bmphead,sizeof(BMP_HEADER),&br);
if(res!=FR_OK)return 2;
res=f_read(&fp,&bmpinfo,sizeof(BMP_INFO),&br);
if(res!=FR_OK)return 2;
// printf('图片类型:%c%crn',bmphead.bfType>>8,bmphead.bfType);
// printf('图片尺寸:%d*%drn',bmpinfo.biWidth,bmpinfo.biHeight);
// printf('颜色位数:%drn',bmpinfo.biBitCount);
w=bmpinfo.biWidth;
h=bmpinfo.biHeight;
u32 oneline_size=bmpinfo.biWidth*3;//一行的字节数
u32 read_oneline_size=oneline_size;//要读取的一行字节数
/*取出有效的rgb颜色值的一行字节数*/
while(oneline_size%4)oneline_size++;/*保存一行字节数为4的倍数*/
u32 addr=bmphead.bfOffBits+(bmpinfo.biHeight-1)*oneline_size;
/*将指针偏移到最后一行*/
u32 i=0,j=0;;
u32 rgb888;
u32 cnt=0;
for(i=0;i
3.5 主函数main.c
在主函数main.c中主要完成各个外设初始化、SD卡挂载、图片获取、触摸屏坐标和按键值获取,最终实现通过触摸屏滑动或者按下切换图片。
FATFS fs;
int main()
{
u8 key;
STM32_Clock_Init(16);
Beep_Init();
Led_Init();
Key_Init();
Usartx_Init(USART1,115200,128);
TIMx_Init(TIM2,128,20*1000);
W25Q64_Init();//W25Q64初始化
IIC_Init();//IIC初始化
NT35310_Init();//LCD初始化
XPT2046_Init();
TOUCH_Calibration();//触摸屏校准
printf('触摸屏校准完成rn');
SRAM_Init();
u8 res;
AA:
res=f_mount(&fs,'0',1);
if(res)
{
LCD_Clear(LIGHTBLUE);
LCD_Display_Str(LCD_WIDTH/2-strlen('请检查SD卡是否插好!')*12/2,210,24,(u8 *)'请检查SD卡是否插好!',RED);
LCD_Display_Str(LCD_WIDTH/2-strlen('注意文件系统格式须为FAT32!')*12/2,240,24,(u8 *)'注意文件系统格式须为FAT32!',RED);
LCD_Refresh();//更新显示
Delay_Ms(1000);
goto AA;
}
BB:
LCD_Clear(LIGHTBLUE);
res=SDCard_PrintDir('photo');//遍历目录
if(res)
{
LCD_Refresh();//更新显示
LCD_Display_Str(LCD_WIDTH/2-strlen('BMP图片不存在!')*12/2,210,24,(u8 *)'BMP图片不存在!',RED);
LCD_Display_Str(LCD_WIDTH/2-strlen('请将图片存储在/photo下')*12/2,240,24,(u8 *)'请将图片存储在/photo下',RED);
LCD_Refresh();//更新显示
Delay_Ms(1000);
goto BB;
}
LCD_Clear(LIGHTBLUE);
LCD_Display_Str(LCD_WIDTH/2-strlen('正在加载图片。。')*12/2,210,24,(u8 *)'正在加载图片。。',RED);
LCD_Refresh();//更新显示
Delay_Ms(1000);
FILE_INFO *bmp_temp=bmp_head;
if(bmp_temp->next!=NULL)
{
bmp_temp=bmp_temp->next;
BMP_Display(bmp_temp->file_name,bmp_temp->number);
}
u16 x1,x2;
int stat=0;
while(1)
{
res=XPT2046_ReadXY();
if(res)
{
x1=touch_info.x;
while(T_PEN==0)//等待松开
{
XPT2046_ReadXY();
x2=touch_info.x;
}
if(x1-x2>50)stat=1;
else if(x2-x1>50)stat=2;
}
key=Key_Scan();
if(key==1 || stat==1)
{
stat=0;
BEEP=1;
Delay_Ms(50);
BEEP=0;
if(bmp_temp->next!=NULL)
{
bmp_temp=bmp_temp->next;
BMP_Display(bmp_temp->file_name,bmp_temp->number);
}
}
else if(key==2 || stat==2)
{
stat=0;
BEEP=1;
Delay_Ms(50);
BEEP=0;
if(bmp_temp->pre!=NULL && bmp_temp->pre->file_name[0]!=0)
{
bmp_temp=bmp_temp->pre;
BMP_Display(bmp_temp->file_name,bmp_temp->number);
}
//printf('%srn',bmp_temp->file_name);
}
}
}