目录
第一部分、基础知识
1、SD卡读取数据原理
1.1、SD卡介绍
FPGA开发板上的 SD 卡接口一般都为小卡的设计,简称Micro SD卡(原名 TF 卡),是一种极细小的快闪存储器卡, 是由 SanDisk(闪迪)公司发明, 主要用于移动手机。MicroSD 卡接口定义以及各引脚功能说明如下图:
1.2、ZYNQ内部添加FATFS文件系统
ZYNQ中关于SD卡的驱动和控制,已经提供给开发者了。因此在开发的过程中不需要再浪费时间来研究SD卡的驱动程序。在使用的过程中只需要调用 FAT 文件系统模块,就可以实现对SD卡的读写操作。
Xilinx SDK 的standalone 已经移植好了 FATFS 文件系统,在 SDK 中添加 xilffs 库后, 就可以在程序中使用 FATFS 中的 API 函数来操作SD卡。具体调用方式如下:
第一步、查看开发板的原理图,熟悉SD卡的连接
下图为我使用的开发板原理图,其中数据管脚和时钟管脚最终连接在Bank501上面,而bank501的驱动电压为1.8V,实际TF卡的工作电压为3.3V。因此这里使用一个电平转换芯片(TXS02612RTWR),实现了 1.8V 和 3.3V。
注意:我使用的开发板CD端口接地了,CD信号是TF卡检测信号,用于标志开发板是否连接了TF卡。
第二步、ZYNQ系统的配置
第三步、借助SDK软件,添加FATFS文件系统
注意可能会添加失败的原因:
1、添加时最好是新建一个空的工程,然后再添加。如果在原有的Application Project上直接添加好像会失败。
2、添加时,先关闭 system.mss 的界面,然后再添加 FATFS库,不然也可能导致 FATFS 库添加失败。详细步骤参考正点原子数据手册。
1.3、FATFS系统的API函数使用方法
关于FATFS系统的API函数的详解:FATFS系统所有API函数使用说明
2、BMP格式图片读取原理
2.1、关于BMP格式图片的介绍
我们常用的图片格式一般最常用的有四种: JPG、 BMP、 PNG 和 GIF。其中 JPG、 BMP 和 PNG 是静态图片,而 GIF 则是可以实现动态图片。
BMP 全称是 Bitmap(位图)的缩写,其特点是几乎不进行压缩,由此导致了它与生俱来的缺点,即占用磁盘空间较大;而其它三种图片格式均进行了不同程度的压缩,以节省磁盘空间。在本次实验中,我们选择使用不压缩的 BMP 图片格式,因为解析该格式的图片最为简单。
需要注意:
(1)、BMP 图片的数据格式,首先要读取 BMP 文件前面 54 个字节的图片头数据,头数据其中包含了 BMP 图片的分辨率等信息。
(2)、BMP 图像数据格式是 BGR,以 BMP 24位真彩色为例,颜色分量“ B”位于低地址位,颜色分量“ G”位于中间地址位,颜色分量“ R”位于高地址位。
(3)、BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序, 因此如果我们直接将一整幅图片存入 DDR 显存,那么最终显示出来的将是一个上下颠倒的图片
2.2、纠正BMP数据颠倒的代码
这里面的frame是一个u8类型的指针(因为u8指针自加的时候,每次只步进一个字节,对应f_read函数),指向的是DDR内部的地址(范围:0008_0000-3FFF_FFFF)。用来存储图片数据的DDR首地址必须在这个1GB范围之内,因为AXI HP接口能访问的区间只能这个范围。
其次是frame_col这个u8类型的指针,指向的地址是DDR用来存储每一行图片数据的第一个字节。原理如下图的所示:
其次 f_read函数读取BMP图片时会自动从前往后读。但是DDR内部地址的步长为32bit(如下图所示),也就是4个字节,因此这里用4个字节来存储一个像素点。而一个彩色像素点只有24bit,只占3个字节,因此这里每次读取长度为3字节,排列为RGB,R在高位,G在低位。然后最高位字节补00。
这样操作的目的是为了配和AXI HP接口读模块,接口读模块内部的异步fifo写位宽为32位。
具体的读取数据,存入正确的数据顺序代码原理如下:
//这种写入内存的方法才是对的
for(int i = 0;i<*bmp_height;i++)
{
frame_col = frame + (1080 - i - 1)*1920*4;//最后一行的第一个列开始显示
for(int j = 0; j<*bmp_width; j++)
{
f_read(&fil,frame_col,3,&br);
*(frame_col+3) = 0x00;//对应我的fifo读取数据的方式
frame_col = frame_col + 4;
}
}
2.3、AXI HP访问的DDR地址范围
AXI HP接口能够访问DDR内部的地址范围:0008_0000-3FFF_FFFF。(恰好是1GB)
第二部分、软件代码
关于代码的设计大部分来自正点原子的参考手册,但是关于读取图片数据和存储图片数据的代码是按照我自己的想法设计的,设计原理参考上面。
1、sd_card.h文件
/*
* sd_card.h
*
* Created on: 2023年12月17日
* Author: dpt
*/
#ifndef SRC_SD_CARD_H_
#define SRC_SD_CARD_H_
#define FILE_NAME "test.txt" //定义文件名
#define PICTURE_NAME1 "Camaro1.bmp" //定义图片文件名(和SD卡内的保持一致)
#define PICTURE_NAME2 "Camaro2.bmp"
#define PICTURE_NAME3 "Camaro3.bmp"
void init_sd_card(); //加载SD设备
int sd_read_data(char *file_name,u32 src_addr,u32 byte_len);//SD卡读数据
int sd_write_data(char *file_name,u32 src_addr,u32 byte_len);//SD卡写数据
void load_sd_bmp(u8 *frame, u8 cnt);//读取图片数据
#endif /* SRC_SD_CARD_H_ */
2、sd_card.c文件
/*
* sd_card.c
*
* Created on: 2023年12月17日
* Author: dpt
*/
#include "xparameters.h"
#include "xil_printf.h"
#include "ff.h"
#include "sd_card.h"
//文件系统变量
static FATFS fatfs;
//挂在函数返回不同值的含义(翻译版)
static const char * FR_Table[]=
{
"FR_OK:成功", /* (0) Succeeded */
"FR_DISK_ERR:底层硬件错误", /* (1) A hard error occurred in the low level disk I/O layer */
"FR_INT_ERR:断言失败", /* (2) Assertion failed */
"FR_NOT_READY:物理驱动没有工作", /* (3) The physical drive cannot work */
"FR_NO_FILE:文件不存在", /* (4) Could not find the file */
"FR_NO_PATH:路径不存在", /* (5) Could not find the path */
"FR_INVALID_NAME:无效文件名", /* (6) The path name format is invalid */
"FR_DENIED:由于禁止访问或者目录已满访问被拒绝", /* (7) Access denied due to prohibited access or directory full */
"FR_EXIST:由于访问被禁止访问被拒绝", /* (8) Access denied due to prohibited access */
"FR_INVALID_OBJECT:文件或者目录对象无效", /* (9) The file/directory object is invalid */
"FR_WRITE_PROTECTED:物理驱动被写保护", /* (10) The physical drive is write protected */
"FR_INVALID_DRIVE:逻辑驱动号无效", /* (11) The logical drive number is invalid */
"FR_NOT_ENABLED:卷中无工作区", /* (12) The volume has no work area */
"FR_NO_FILESYSTEM:没有有效的FAT卷", /* (13) There is no valid FAT volume */
"FR_MKFS_ABORTED:由于参数错误f_mkfs()被终止", /* (14) The f_mkfs() aborted due to any parameter error */
"FR_TIMEOUT:在规定的时间内无法获得访问卷的许可", /* (15) Could not get a grant to access the volume within defined period */
"FR_LOCKED:由于文件共享策略操作被拒绝", /* (16) The operation is rejected according to the file sharing policy */
"FR_NOT_ENOUGH_CORE:无法分配长文件名工作区", /* (17) LFN working buffer could not be allocated */
"FR_TOO_MANY_OPEN_FILES:当前打开的文件数大于_FS_SHARE", /* (18) Number of open files > _FS_SHARE */
"FR_INVALID_PARAMETER:参数无效" /* (19) Given parameter is invalid */
};
//加载SD设备
void init_sd_card()
{
int status;
TCHAR *Path = "0:/";
status = f_mount(&fatfs, Path, 1); //挂载SD卡,0: 现在不要安装(要安装在第一次访问卷) 1: 强制安装卷,以检查它是否准备工作。
if (status != FR_OK)
{
xil_printf("挂载文件系统失败 (%s)\r\n", FR_Table[status]);
}
else
{
xil_printf("挂载文件系统成功 (%s)\r\n", FR_Table[status]);
}
}
//SD卡读数据
int sd_read_data(char *file_name,u32 src_addr,u32 byte_len)
{
FIL fil; //文件对象
UINT br; //f_read函数返回已读出的字节数
//打开一个只读的文件
f_open(&fil,file_name,FILE_NAME);
//移动打开的文件对象的文件读/写指针 0:指向文件开头
f_lseek(&fil,0);
//从SD卡中读出数据
f_read(&fil,(void*)src_addr,byte_len,&br);
//关闭文件
f_close(&fil);
return 0;
}
//SD卡写数据
int sd_write_data(char *file_name,u32 src_addr,u32 byte_len)
{
FIL fil; //文件对象
UINT bw; //f_write函数返回已写入的字节数
//打开一个文件,如果不存在,则创建一个文件
f_open(&fil,file_name,FA_CREATE_ALWAYS | FA_WRITE);
//移动打开的文件对象的文件读/写指针 0:指向文件开头
f_lseek(&fil, 0);
//向文件中写入数据
f_write(&fil,(void*) src_addr,byte_len,&bw);
//关闭文件
f_close(&fil);
return 0;
}
//从SD卡中读取BMP图片
void load_sd_bmp(u8 *frame, u8 cnt)
{
static FATFS fatfs;
FIL fil;
UINT *bmp_width,*bmp_height,*bmp_size;
u8 bmp_head[54];
UINT br;
u8 *frame_col;
int i;
//挂载文件系统
f_mount(&fatfs,"",1);
//打开文件
if(cnt == 0)
f_open(&fil,PICTURE_NAME1,FA_READ);
else if(cnt == 1)
f_open(&fil,PICTURE_NAME2,FA_READ);
else if(cnt == 2)
f_open(&fil,PICTURE_NAME3,FA_READ);
//移动文件读写指针到文件开头
f_lseek(&fil,0);
//读取BMP文件头
f_read(&fil,bmp_head,54,&br);
xil_printf("picture head: \n\r");
for(i=0;i<54;i++)
xil_printf(" %x",bmp_head[i]);
//打印BMP图片分辨率和大小
bmp_width = (UINT *)(bmp_head + 0x12);//1920*3*
bmp_height = (UINT *)(bmp_head + 0x16);//1080
bmp_size = (UINT *)(bmp_head + 0x22);
xil_printf("\n width = %d, height = %d, size = %d bytes \n\r",*bmp_width,*bmp_height,*bmp_size);
//这种写入内存的方法才是对的
for(int i = 0;i<*bmp_height;i++)
{
frame_col = frame + (1080 - i - 1)*1920*4;//最后一行的第一个列开始显示
for(int j = 0; j<*bmp_width; j++)
{
f_read(&fil,frame_col,3,&br);
*(frame_col+3) = 0x00;//对应我的AXI总线fifo读取数据的方式
frame_col = frame_col + 4;
}
}
//关闭文件
f_close(&fil);
// Xil_DCacheFlush(); //刷新Cache,数据更新至DDR3中(如果主函数没有关闭cache,这里就需要刷新到DDR中)
xil_printf("show bmp\n\r");
}
3、main.c文件
/*
* sd_card.c
*
* Created on: 2023年12月17日
* Author: dpt
*/
#include "xparameters.h"
#include "xgpiops.h"
#include "xil_cache.h"//需要关闭cache
#include "sleep.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "ff.h"
#include "sd_card.h"
#define GPIO_DEV_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define frame_buffer_addr 0x2000000 //frame buffer的起始地址
#define RST 54
static XGpioPs GpioPs;
static XGpioPs_Config * GpioCnfPtr;
int initGpio();
int main()
{
u8 cnt = 0;
Xil_DCacheDisable(); //disable cache
// Xil_ICacheDisable();
initGpio();
init_sd_card();
XGpioPs_WritePin(&GpioPs,RST,0x00); //CMOS_RST SET 0
XGpioPs_WritePin(&GpioPs,RST,0x01);//CMOS_RST SET 1
XGpioPs_WritePin(&GpioPs,RST,0x00); //CMOS_RST SET 0
while(1)
{
load_sd_bmp((u8 *)frame_buffer_addr,cnt++);//读取图片数据,并且写入到DDR当中
if(cnt > 2) cnt = 0;
sleep(1);
}
return 0;
}
//initial gpio func
int initGpio(){
int status;
GpioCnfPtr = XGpioPs_LookupConfig(GPIO_DEV_ID);
status = XGpioPs_CfgInitialize(&GpioPs,GpioCnfPtr,GpioCnfPtr->BaseAddr);
if(status != XST_SUCCESS){
return status;
}
XGpioPs_SetDirectionPin(&GpioPs,RST,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,RST,0x01);
return status;
}
第三部分、总结
1、写在最后
更多的SD卡使用细节可以参考正点原子的《正点原子_领航者ZYNQSDK开发指南》PDF的第十三和二十一章节。
其次,本篇博客记录了我调试SD卡时的思路和过程,希望能够对你开发提供帮助!⛲⛲⛲
2、工程代码
这篇文章是为了下面这篇文章的内容做铺垫,关于工程下载链接以及实验现象,请参考这篇文章【ZYNQ实验】第一篇、ZYNQ驱动HDMI显示图片-CSDN博客