0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于ST32F103ZET6设计的小说阅读器详解

DS小龙哥-嵌入式技术 来源:DS小龙哥-嵌入式技术 作者:DS小龙哥-嵌入式技 2022-02-28 13:37 次阅读

一、环境介绍

小车主控MCU: STM32F103ZET6

STM32程序开发IDE: keil5

STM32程序风格: 采用寄存器方式开发,注释齐全,执行效率高,方便移植

硬件包含:一块STM32F103ZET6系统板、一个2.8寸TFT电阻触摸显示屏、一个SD卡卡槽(SPI接口)、一张SD卡(存放字库和小说文件)

二、功能介绍

这是基于ST32F103ZET6设计的小说阅读器,虽然对于真实的小说阅读器产品来讲,实用性和功能方面还差很多,但是对于刚入门的STM32、单片机开发工程师来讲,这里面设计到的技术才是最有价值的。

所以这篇文章的小说阅读器主要是用来作为嵌入式单片机工程师入门练手项目、大学生的课程设计等。目的不在于小说阅读器,而是以小说阅读器为例子,学习相关的技术: SD卡、串口通信、SPI通信、8080时序、触摸屏校准原理、FATFS文件系统使用、语音播报模块使用等等。

该阅读器支持常规阅小说读器具备的基本功能:

1. 支持选择指定的小说进行查看阅读,可以通过触摸屏上的按钮进行切换。

2. 支持切换字体大小

3. 支持切换字体颜色、背景颜色

4. 标题栏显示当前阅读器查看的小说文件名称

5. 支持翻页、上一页、下一页

6. 支持语音自动阅读,发声接近正常真人发声,非常强大。

语音方案可以选择两种: (1).宇音SYN6658 (2). 科大讯飞SYN5152。 这两款芯片都是通过串口通信,编程十分简单。

内部编程思路介绍:

小说阅读器的字体是存放在SD卡上的,SD卡采用SPI接口的卡槽与STM32相连接,STM32配合FATFS文件系统对SD卡上的文件进行操作;为了提高访问效率、在第一次上电的时候会将SD卡上的字库文件拷贝到板载W25Q64芯片内。小说文件还是存放在SD卡上,每次翻页的时候从SD卡上获取文本文件,渲染到LCD显示屏上。

该显示屏是2.8寸的电阻触摸显示屏,驱动芯片是ILI9341(兼容:9325,9328),LCD的引脚接线兼容正点原子的2.8寸LCD显示屏;电阻屏的驱动芯片是XPT2046,,是很常见的组合,这个XPT2046就是个ADC芯片,最终要完成触摸屏上坐标点定位,还需要自己写校准算法进行换算。 ILI9341驱动芯片支持8080时序操作,可以采用IO模拟方式驱动、也可以采用STM32的FSMC接口驱动。 STM32增强版支持FSMC功能的,其他没有FSMC接口的芯片,可以采用模拟8080时序方式驱动,效果一样,只是效率上差点,无法实现高速刷屏,只要不进行高速刷屏,凑合使用是没什么问题的。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

三、所用到的硬件介绍(都是淘宝买的)

3.1 STM32F103ZET6最小系统板

这是在淘宝上买的硬件详情,开发板和LCD用哪一款都可以的,编程思路都是一样。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

开发板的板载资源如下:
CPU:STM32F103ZET6,LQFP144,FLASH:512K,SRAM:64K;
外扩SPI FLASH:W25Q32,8M字节;
1个电源指示灯;
2个状态指示灯;
一个EEPROM芯片,24C02,容量256字节(注意:不同产地标号不一,但都是24C02芯片,经测试无误)
1个光敏传感器
1个无线模块接口,可接NRF24L01/RFID/CC01模块;
1路CAN接口,采用TJA1050芯片;
1路485接口,采用SP485芯片;
1个标准的2.4/2.8/3.5/4.3/7寸LCD接口,支持触摸屏;
一个USB串口,可用于程序下载和代码调试(USMART调试);
1个USB SLAVE接口,用于USB通信;
1个复位按键;
2个独立按键;
1个SD卡座,用来接SD卡;
1个RTC后备电池座;
1个标准的JTAG/SWD仿真下载调试接口;
1路5V转3.3V电路;
芯片引脚144个脚全部引出,方便外接扩展实验;
1个电源开关,用来开关USB的电源;

3.2 SD卡卡槽

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

3.3 SYN6658语音合成芯片

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

功能特点:
• 芯片支持任意中文文本的合成,可以采用GB2312、GBK、BIG5 和Unicode 四种编码方式;
• 芯片具有文本智能分析处理功能,对常见的数值、电话号码、时间日期、度量衡符号等格式的文本;
• 芯片可以自动对文本进行分析,判别文本中多音字的读法并合成正确的读音;
• 芯片可实现10级数字音量控制,音量更大,更广;
• 芯片内集成了77首声音提示音和14首和弦音乐;
• 提供两男、两女、一个效果器和一个女童声共6个中文发音人;
• 支持多种文本控制标记,提升文本处理的正确率;
• 支持多种控制命令,包括:合成、停止、暂停合成、继续合成、改变波特率等;
• 支持多种方式查询芯片的工作状态;
• 两种通讯模式:芯片支持UART、SPI两种通讯方式;
• 芯片支持Power Down 模式。使用控制命令可以使芯片进入Power Down 模式;
• 芯片支持的通讯波特率:4800bps,9600bps,57600bps、115200bps;
• 芯片各项指标均满足室外严酷环境下的应用;

应用范围:
• 车载信息终端语音播报,车载调度,车载导航
• 停车场收费系统/诱导系统
• 公交报站器 ,考勤机
手机,固定电话
• 排队叫号机,收银收费机
• 自动售货机,信息机, POS 机
• 智能仪器仪表 ,气象预警机,智能变压器
• 智能玩具,智能手表
• 电动自行车
• 语音电子书,彩屏故事书,语音电子词典,语音电子导游
• 短消息播放 ,新闻播放
• 电子地图

四、操作说明

4.1 程序下载

开发板支持Jlink下载、也支持串口下载。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

4.2屏幕操作说明

目前实现的功能:
1. 小说翻页:支持点击触摸屏按钮翻下一页显示
2. 换小说:点击触摸屏按钮“下一本”,可以切换小说。
3. 换颜色:点击触摸屏按钮“颜色调整”,可以切换颜色,支持12种字体颜色切换。
4. 换字体:点击触摸屏按钮“字体调整”,可以切换字体,目前支持两种字体(16X16 24X24)。

思路说明:
程序里移植了FATFS文件系统,字体文件和小说文件都是存放在SD卡,通过文件系统读取SD卡里的小说文件进行显示。

操作的过程在串口调试助手上也会同步输出信息。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

4.3 校准说明

第一次使用,需要校准屏幕,否则触摸屏没有反应。

如果发现屏幕不灵敏,可以强制进行校准,按下按键K2再按下复位键即可进行强制校准。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

依次点击屏幕上4个红圈。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

4.4 SD卡上存放的文件

SD卡上有两个目录:font目录和txt目录。

font目录:存放字库文件。有两个字库字体。

txt目录:存放小说文件,内置了3篇小说。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

五、核心代码

代码采用Keil5编写,下载即可编译,测试,学习。

工程完整源码下载地址:基于STM32设计的小时阅读器完整源码(不懂可以私信).zip-嵌入式文档类资源-CSDN下载

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

5.1 main.c 主函数代码

#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include 
#include 
#include "iic.h"
#include "at24c08.h"
#include "w25q64.h"
#include "nt35310_lcd.h"
#include "xpt2046.h"
#include "sdcard.h"
#include "ff.h"  //FATFS文件系统的头文件

//更新字库---从SD卡读取字库到W25Q64
void FontUpdate_to_W25Q64();
    
FATFS fatfs; //文件系统注册工作区需要使用

u16 select_color[]={WHITE,BLACK,BLUE,RED,YELLOW,BROWN,BRRED,GRAY,DARKBLUE,LIGHTBLUE,GRAYBLUE,LIGHTGREEN};
u8 read_text_buf[4096+1];
int main()
{  
    u32 x;u32 y;u32 size=16;u8 *p;
    u8 color_select_cnt=0; //12个
    FIL text_file;
    u16 br=0;
    u8 r_data=10;
    u32 read_cnt=0;
    DIR dir;
    FRESULT res; 
    FILINFO fno; //存放读取的文件信息
    char *abs_path=NULL;  
    char path[]="0:/txt";
	u32 cnt=0;
	USART_X_Init(USART1,72,115200);
    
    NT35310_LcdInit();
	NT35310_Clear(WHITE);
    
	IIC_Init(); //IIC总线初始化
	W25Q64_Init(); //初始化W25Q64
	
	TOUCH_Init(); 	 //触摸屏初始化
	TOUCH_CheckXY(); //触摸屏校准程序
	
    RCC->APB2ENR|=1<<5;
    GPIOD->CRH&=0xFF0FFFFF;
    GPIOD->CRH|=0x00300000;
    while(SDCardDeviceInit()!=0)
    {
        printf("SDCard_DeviceInit 错误.\r\n");
        PDout(13)=!PDout(13);
        delay_ms(100);
    }
    
	f_mount(&fatfs,"0:",0); //注册文件系统的工作区

    //设计界面
    LCD_color_1=RED;
    LCD_color_2=LIGHTBLUE;
	NT35310_DisplayString(16,0,16,"基于STM32的小说阅读器设计");
    NT35310_DrawLine(0,16,239,16,DARKBLUE);
    
    //绘制按键
    NT35310_DrawRectangle(0,319-80,239,319,RED);
    NT35310_DrawLine(0,319-40,239,319-40,DARKBLUE);
    NT35310_DrawLine(239/2,319-80,239/2,319,DARKBLUE);
    
    LCD_color_2=WHITE;
    NT35310_DisplayString(32,319-70,16,"下一页");
    NT35310_DisplayString(239/2+32,319-70,16,"下一本");
    NT35310_DisplayString(32,319-30,16,"字体调整");
    NT35310_DisplayString(239/2+32,319-30,16,"颜色调整");
    
     /*1. 打开目录*/    
    res=f_opendir(&dir,path);
    if(res!=FR_OK)return res;
    
    res=f_readdir(&dir,&fno);
    printf("文件名称: %s,文件大小: %ld 字节\r\n",fno.fname,fno.fsize);
    
    LCD_color_1=BLACK;
    NT35310_DisplayString(0,17,16,fno.fname);
    
    if(abs_path)
    {
         free(abs_path);
         abs_path=NULL;
    }
    
    //申请存放文件名称的长度
    abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
    
    strcpy(abs_path,path);
    strcat(abs_path,"/");
    strcat(abs_path,fno.fname);

    printf("abs_path=%s\n",abs_path);
    

    NT35310_DisplayString(0,17+16,16,"第1卷\
第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀\
此开卷第一回也。作者自云:因曾历过一番梦幻之后,故将真事隐去,\
    而借“通灵”之说,撰此<<石头记>>一书也。故曰“甄士隐”云云。\
    但书中所记何事何人?自又云:“今风尘碌碌,一事无成,忽念及当日所有之女子,\
    一一细考较去,觉其行止见识,皆出于我之上。何我堂堂须眉,诚不若彼裙钗哉?");
    
	while(1)
    {
        if(TOUCH_PEN==0) //判断触摸屏是否按下
        {
            //判断是否读取到XY坐标
            if(TOUCH_ReadXY())
            {	
               // printf("x=%d,y=%d\r\n",touch_info.x,touch_info.y);

                //判断范围
                if((touch_info.x>=0 && touch_info.x<=239/2)&&
                (touch_info.y>=319-80 && touch_info.y<=319-40))
                {
                    LCD_color_2=BLUE;
                    //填充颜色
                    NT35310_Fill(0+1,319-80+1,239/2-1,319-40-1,BLUE);
                    //显示字符串
                    NT35310_DisplayString(32,319-70,16,"下一页");
                    
                    //等待触摸屏松开
                    while(TOUCH_PEN==0){}
                    
                    //填充颜色--清屏
                    NT35310_Fill(0,18+16,239,319-80-1,WHITE);
                        
                    LCD_color_2=WHITE;
                    if(read_cnt>=br)
                    {
                        read_cnt=0;
                    }
                    if(read_cnt==0)
                    {
                        if(br!=4096)
                        {
                            res=f_open(&text_file,(const TCHAR*)abs_path,FA_READ);//打开文件	 
                            if(res!=0)
                            {
                                printf("%s文件打开失败!\r\n",abs_path);	
                                return 1;  //文件打开失败
                            }        
                            printf("%s文件打开成功!\n",abs_path);	
                        }
                      //执行代码                        
                        res=f_read(&text_file,read_text_buf,4096,(UINT*)&br);//读出4096个字节
                        read_text_buf[br]='\0';
                        printf("br=%d\r\n",br);
                        if(br!=4096)
                        {
                            f_close(&text_file);
                        }
                    }
                   
                    //字体大小
                    
                    x=0;     //坐标起始位置
                    y=17+16; //坐标起始位置
                    p=read_text_buf+read_cnt;
                    while(*p!='\0')
                    {
                            if(*p>0x80) //判断是否是中文-编码规则从 0X8140 开始
                            {
                                    read_cnt+=2;
                                    if(x+size>239)
                                    {
                                            x=0; //横坐归0
                                            y+=size; //换行
                                            if(y+size>=319-80-1)break;
                                    }
                                    NT35310_DisplayGBKData(x,y,size,p);//显示一个中文
                                    x+=size;
                                    p+=2; //偏移两个字节
                            }
                            else if(*p>=' ' && *p<='~')  //常用的ASCII码
                            {
                                    read_cnt+=1;
                                    if(x+size/2>239)
                                    {
                                            x=0; //横坐归0
                                            y+=size; //换行
                                            if(y+size>=319-80-1)break;
                                    }
                                    if(size==16)
                                    {
                                        //显示英文字母
                                        NT35310_DisplayData(x,y,size/2,size,(u8*)ASCII_8_16[*p-' ']);
                                    }
                                    else if(size==24)
                                    {
                                        //显示英文字母
                                        NT35310_DisplayData(x,y,size/2,size,(u8*)asc2_2412[*p-' ']);
                                    }
                                   
                                    p+=1;
                                    x+=size/2;
                            }
                            else if(*p=='\n')
                            {
                                    x=0;
                                    y+=size;
                                    p+=1; //偏移指针
                                    read_cnt+=1;
                                    if(y+size>=319-80-1)break;
                            }
                            else 
                            {
                                    read_cnt+=1;
                                    p+=1; //偏移指针
                            }
                    }
                        
                    //填充颜色
                    NT35310_Fill(0+1,319-80+1,239/2-1,319-40-1,WHITE);
                    LCD_color_2=WHITE;
                    //显示字符串
                    NT35310_DisplayString(32,319-70,16,"下一页");   
                }
 
                 //判断范围
                if((touch_info.x>=239/2 && touch_info.x<=239)&&
                (touch_info.y>=319-80 && touch_info.y<=319-40))
                {
                    LCD_color_2=BLUE;
                    //填充颜色
                    NT35310_Fill(239/2+1,319-80+1,239-1,319-40-1,BLUE);
                    //显示字符串
                    NT35310_DisplayString(239/2+32,319-70,16,"下一本");
                    
                    //等待触摸屏松开
                    while(TOUCH_PEN==0){}
                    LCD_color_2=WHITE;
                    
                     //关闭原来的文件
                    f_close(&text_file);
                        
                    //触发新的页
                    read_cnt=0;
                    br=0;
                    //执行代码                        
                    res=f_readdir(&dir,&fno);
                    if(fno.fname[0] == 0 || res!=0)
                    {
                        /*3. 关闭目录*/
                        f_closedir(&dir);
                        
                         /*1. 打开目录*/    
                        res=f_opendir(&dir,path);
                        if(res!=FR_OK)return res;
                        
                        res=f_readdir(&dir,&fno);
                    }
                        
                    printf("文件名称: %s,文件大小: %ld 字节\r\n",fno.fname,fno.fsize);
                    
                    LCD_color_1=BLACK;
                    NT35310_DisplayString(0,17,16,fno.fname);
                    
                    if(abs_path)
                    {
                         free(abs_path);
                         abs_path=NULL;
                    }
                    //申请存放文件名称的长度
                    abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
                    
                    strcpy(abs_path,path);
                    strcat(abs_path,"/");
                    strcat(abs_path,fno.fname);

                    printf("abs_path=%s\n",abs_path);
                        
                    //填充颜色
                    NT35310_Fill(239/2+1,319-80+1,239-1,319-40-1,WHITE);
                    
                    //显示字符串
                    NT35310_DisplayString(239/2+32,319-70,16,"下一本");   
                }
                
                //判断范围
                if((touch_info.x>=0 && touch_info.x<=239/2)&&
                (touch_info.y>=319-40 && touch_info.y<=319))
                {
                    LCD_color_2=BLUE;
                    //填充颜色
                    NT35310_Fill(0+1,319-40+1,239/2-1,319-1,BLUE);
                    //显示字符串
                     NT35310_DisplayString(32,319-30,16,"字体调整");
                    
                    //等待触摸屏松开
                    while(TOUCH_PEN==0){}
                    
                     if(size==16)size=24;
                     else size=16;
                        
                    //执行代码                        
                        
                    //填充颜色
                    NT35310_Fill(0+1,319-40+1,239/2-1,319-1,WHITE);
                    LCD_color_2=WHITE;
                    //显示字符串
                     NT35310_DisplayString(32,319-30,16,"字体调整");   
                }
                
                
                 //判断范围
                if((touch_info.x>=239/2 && touch_info.x<=239)&&
                (touch_info.y>=319-40 && touch_info.y<=319))
                {
                    LCD_color_2=BLUE;
                    //填充颜色
                    NT35310_Fill(239/2+1,319-40+1,239-1,319-1,BLUE);
                    //显示字符串
                    NT35310_DisplayString(239/2+32,319-30,16,"颜色调整");
                    
                    //等待触摸屏松开
                    while(TOUCH_PEN==0){}
                    
                    //执行代码  
                    //前景字体颜色切换
                    LCD_color_1=select_color[color_select_cnt++];                      
                    if(color_select_cnt>=12)
                    {
                        color_select_cnt=0;
                    }
                       
                    //填充颜色
                    NT35310_Fill(239/2+1,319-40+1,239-1,319-1,WHITE);
                    LCD_color_2=WHITE;
                    //显示字符串
                    NT35310_DisplayString(239/2+32,319-30,16,"颜色调整");   
                }
            }	
        }
    }
}

u32 gbk32_32_addr=1024*0;
u8 font_buffer[4096];

//更新字库---从SD卡读取字库到W25Q64
void FontUpdate_to_W25Q64()
{
    u32 w_cnt=0;
	FILINFO fno;
	FIL fp;
	UINT br,res;
    /*1. 打开字库*/
	f_open(&fp,"0:/font/gbk16.DZK",FA_READ);
	
	/*2. 循环读取字库更新到W25Q64*/
	f_stat("0:/font/gbk16.DZK",&fno);
	printf("文件的大小:%d\r\n",fno.fsize);
	while(1)
	{
		 /*3. 读取字库文件*/
		 res=f_read(&fp,font_buffer,4096,&br);
		 /*4. 写入到W25Q64里*/
		 W25Q64_WriteData(gbk32_32_addr,br,font_buffer);
		 gbk32_32_addr+=br;
		 w_cnt+=br;
		 printf("font16:%.f%%\r\n",(w_cnt*1.0/fno.fsize)*100);
		
		 /*5. 判断文件是否结束*/
		 if(res!=FR_OK||br!=4096)break;
	}
	/*6. 关闭字库文件*/
	f_close(&fp);
}

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

5.2 sdcard.c SD卡驱动代码

#include "sdcard.h"			   
static u8  SD_Type=0;  //存放SD卡的类型

/*
函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
函数参数:data是要写入的数据
返 回 值:读到的数据
说明:时序是第二个上升沿采集数据
*/
u8 SDCardReadWriteOneByte(u8 DataTx)
{		 
  u8 DataRx;				 
  u8 i;
  for(i=0;i<8;i++)
	{
		SDCardSCLK(0);  
		if(DataTx&0x80){SDCardOut(1);}
		else {SDCardOut(0);}
		DataTx<<=1; 
		SDCardSCLK(1);//第二个上升沿采集数据
		DataRx<<=1;
		if(SDCardInput)DataRx|=0x01;
	}
	return DataRx;
}

/*
函数功能:底层SD卡接口初始化

本程序SPI接口如下:
PC11  片选 SDCardCS
PC12  时钟 SDCardSCLK
PD2   输出 SPI_MOSI--主机输出从机输入
PC8   输入 SPI_MISO--主机输入从机输出
*/
void SDCardSpiInit(void)
{
 	RCC->APB2ENR|=1<<5;		    //使能PORTD时钟
	RCC->APB2ENR|=1<<4;		    //使能PORTC时钟
	
	GPIOD->CRL&=0XFFFFF0FF; 
	GPIOD->CRL|=0X00000300;	  //PD2	    
	GPIOD->ODR|=1<<2;    	    //PD2

	GPIOC->CRH&=0XFFF00FF0;   
	GPIOC->CRH|=0X00033008;
	
	GPIOC->ODR|=0X3<<11;
	GPIOC->ODR|=1<<8;
	SDCardCS(1);
}


/*
函数功能:取消选择,释放SPI总线
*/
void SDCardCancelCS(void)
{
	SDCardCS(1);
 	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
}

/*
函数 功 能:选择sd卡,并且等待卡准备OK
函数返回值:0,成功;1,失败;
*/
u8 SDCardSelectCS(void)
{
	SDCardCS(0);
	if(SDCardWaitBusy()==0)return 0;//等待成功
	SDCardCancelCS();
	return 1;//等待失败
}


/*
函数 功 能:等待卡准备好
函数返回值:0,准备好了;其他,错误代码
*/
u8 SDCardWaitBusy(void)
{
	u32 t=0;
	do
	{
		if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
		t++;		  
	}while(t<0xFFFFFF);//等待 
	return 1;
}


/*
函数功能:等待SD卡回应
函数参数:
					Response:要得到的回应值
返 回 值:
					0,成功得到了该回应值
					其他,得到回应值失败
*/
u8 SDCardGetAck(u8 Response)
{
	u16 Count=0xFFFF;//等待次数	   						  
	while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应  	  
	if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败   
	else return SDCard_RESPONSE_NO_ERROR;//正确回应
}


/*
函数功能:从sd卡读取一个数据包的内容
函数参数:
				buf:数据缓存区
				len:要读取的数据长度.
返回值:
			0,成功;其他,失败;	
*/
u8 SDCardRecvData(u8*buf,u16 len)
{			  	  
	if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
    while(len--)//开始接收数据
    {
        *buf=SDCardReadWriteOneByte(0xFF);
        buf++;
    }
    //下面是2个伪CRC(dummy CRC)
    SDCardReadWriteOneByte(0xFF);
    SDCardReadWriteOneByte(0xFF);									  					    
    return 0;//读取成功
}


/*
函数功能:向sd卡写入一个数据包的内容 512字节
函数参数:
					buf 数据缓存区
					cmd 指令
返 回 值:0表示成功;其他值表示失败;
*/
u8 SDCardSendData(u8*buf,u8 cmd)
{	
	u16 t;		  	  
	if(SDCardWaitBusy())return 1;  //等待准备失效
	SDCardReadWriteOneByte(cmd);
	if(cmd!=0XFD)//不是结束指令
	{
		for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间
	    SDCardReadWriteOneByte(0xFF); //忽略crc
	    SDCardReadWriteOneByte(0xFF);
		  t=SDCardReadWriteOneByte(0xFF); //接收响应
		if((t&0x1F)!=0x05)return 2;   //响应错误									  					    
	}						 									  					    
    return 0;//写入成功
}



/*
函数功能:向SD卡发送一个命令
函数参数:
				u8 cmd   命令 
				u32 arg  命令参数
				u8 crc   crc校验值	
返回值:SD卡返回的响应
*/												  
u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)
{
	u8 r1;	
	u8 Retry=0; 
		
	SDCardCancelCS();               //取消上次片选
	if(SDCardSelectCS())return 0XFF;//片选失效 
	//发送数据
	SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令
	SDCardReadWriteOneByte(arg >> 24);
	SDCardReadWriteOneByte(arg >> 16);
	SDCardReadWriteOneByte(arg >> 8);
	SDCardReadWriteOneByte(arg);	  
	SDCardReadWriteOneByte(crc); 
	if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
	Retry=0X1F;

	do
	{
		r1=SDCardReadWriteOneByte(0xFF);
	}while((r1&0X80) && Retry--);	  //等待响应,或超时退出
   return r1;	//返回状态值
}	


/*
函数功能:获取SD卡的CID信息,包括制造商信息
函数参数:u8 *cid_data(存放CID的内存,至少16Byte)	  
返 回 值:
					0:成功,1:错误				
*/
u8 GetSDCardCISDCardOutnfo(u8 *cid_data)
{
    u8 r1;	   
    //发SDCard_CMD10命令,读CID
    r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
    if(r1==0x00)
	  {
			r1=SDCardRecvData(cid_data,16);//接收16个字节的数据	 
    }
	SDCardCancelCS();//取消片选
	if(r1)return 1;
	else return 0;
}	


/*
函数说明:
					获取SD卡的CSD信息,包括容量和速度信息
函数参数:
					u8 *cid_data(存放CID的内存,至少16Byte)	    
返 回 值:
					0:成功,1:错误	
*/
u8 GetSDCardCSSDCardOutnfo(u8 *csd_data)
{
	u8 r1;	 
	r1=SendSDCardCmd(SDCard_CMD9,0,0x01);    //发SDCard_CMD9命令,读CSD
	if(r1==0)
	{
		r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 
	}
	SDCardCancelCS();//取消片选
	if(r1)return 1;
	else return 0;
}  


/*
函数功能:获取SD卡的总扇区数(扇区数)   
返 回 值:
				0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)
说   明:
				每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过.	
*/
u32 GetSDCardSectorCount(void)
{
    u8 csd[16];
    u32 Capacity;  
    u8 n;
	  u16 csize;  					    
    if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;	//取CSD信息,如果期间出错,返回0
    if((csd[0]&0xC0)==0x40)	        //V2.00的卡,如果为SDHC卡,按照下面方式计算
    {	
			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;
}

/*
函数功能: 初始化SD卡
返 回 值: 非0表示初始化失败!
*/
u8 SDCardDeviceInit(void)
{
  u8 r1;      // 存放SD卡的返回值
  u16 retry;  // 用来进行超时计数
  u8 buf[4];  
	u16 i;
	SDCardSpiInit();		//初始化底层IO口
	
 	for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲
	retry=20;
	do
	{
		r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置
	}while((r1!=0X01) && retry--);
 	SD_Type=0;   //默认无卡
	if(r1==0X01)
	{
		if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1)  //SD V2.0
		{
			for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);	//Get trailing return value of R7 resp
			if(buf[2]==0X01&&buf[3]==0XAA)    //卡是否支持2.7~3.6V
			{
				retry=0XFFFE;
				do
				{
					SendSDCardCmd(SDCard_CMD55,0,0X01);	    //发送SDCard_CMD55
					r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41
				}while(r1&&retry--);
				if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
				{
					for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
					if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC;    //检查CCS
					else SD_Type=SDCard_TYPE_V2;   
				}
			}
		}
		else//SD V1.x/ MMC	V3
		{
			SendSDCardCmd(SDCard_CMD55,0,0X01);		//发送SDCard_CMD55
			r1=SendSDCardCmd(SDCard_CMD41,0,0X01);	//发送SDCard_CMD41
			if(r1<=1)
			{		
				SD_Type=SDCard_TYPE_V1;
				retry=0XFFFE;
				do //等待退出IDLE模式
				{
					SendSDCardCmd(SDCard_CMD55,0,0X01);	//发送SDCard_CMD55
					r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41
				}while(r1&&retry--);
			}
			else//MMC卡不支持SDCard_CMD55+SDCard_CMD41识别
			{
				SD_Type=SDCard_TYPE_MMC;//MMC V3
				retry=0XFFFE;
				do //等待退出IDLE模式
				{											    
					r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//发送SDCard_CMD1
				}while(r1&&retry--);  
			}
			if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//错误的卡
		}
	}
	SDCardCancelCS();       //取消片选
	if(SD_Type)return 0;  //初始化成功返回0
	else if(r1)return r1; //返回值错误值	   
	return 0xaa;          //其他错误
}


/*
函数功能:读SD卡
函数参数:
				buf:数据缓存区
				sector:扇区
				cnt:扇区数
返回值:
				0,ok;其他,失败.
说  明:
				SD卡一个扇区大小512字节
*/
u8 SDCardReadData(u8*buf,u32 sector,u32 cnt)
{
	u8 r1;
	if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址
	if(cnt==1)
	{
		r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令
		if(r1==0)												  //指令发送成功
		{
			r1=SDCardRecvData(buf,512);			//接收512个字节	   
		}
	}else
	{
		r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令
		do
		{
			r1=SDCardRecvData(buf,512);//接收512个字节	 
			buf+=512;  
		}while(--cnt && r1==0); 	
		SendSDCardCmd(SDCard_CMD12,0,0X01);	//发送停止命令
	}   
	SDCardCancelCS();//取消片选
	return r1;//
}

/*
函数功能:向SD卡写数据
函数参数:
				buf:数据缓存区
				sector:起始扇区
				cnt:扇区数
返回值:
				0,ok;其他,失败.
说  明:
				SD卡一个扇区大小512字节
*/
u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt)
{
	u8 r1;
	if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址
	if(cnt==1)
	{
		r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令
		if(r1==0)//指令发送成功
		{
			r1=SDCardSendData(buf,0xFE);//写512个字节	   
		}
	}
	else
	{
		if(SD_Type!=SDCard_TYPE_MMC)
		{
			SendSDCardCmd(SDCard_CMD55,0,0X01);	
			SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令	
		}
 		r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令
		if(r1==0)
		{
			do
			{
				r1=SDCardSendData(buf,0xFC);//接收512个字节	 
				buf+=512;  
			}while(--cnt && r1==0);
			r1=SDCardSendData(0,0xFD);//接收512个字节 
		}
	}   
	SDCardCancelCS();//取消片选
	return r1;//
}	

工程完整源码下载地址:https://download.csdn.net/download/xiaolong1126626497/19628524

审核编辑:符乾江

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2270

    文章

    10899

    浏览量

    355887
  • 阅读器
    +关注

    关注

    0

    文章

    301

    浏览量

    27919
收藏 人收藏

    评论

    相关推荐

    STM32F103ZET6为什么无法读取转换结果?

    CPU采用STM32F103ZET6 采用硬件模式并口,我是直接读取16位IO口数据的,但是无法读取到。 时序应该没问题,还是说必须用DMA来读取?
    发表于 12-03 06:58

    集成TIRIS射频模块TMS3705A低频阅读器简介

    电子发烧友网站提供《集成TIRIS射频模块TMS3705A低频阅读器简介.pdf》资料免费下载
    发表于 09-21 11:00 0次下载
    集成TIRIS射频模块TMS3705A低频<b class='flag-5'>阅读器</b>简介

    二代身份证识别仪-身份证阅读器-读卡CICR-3X

    阅读器
    jf_08279201
    发布于 :2024年09月02日 14:33:47

    STM32F103ZET6产生pwm波脉宽可调

    32新手,想请教一下各位用STM32F103ZET6产生pwm波可不可以设计成脉宽可调,就是将占空比每次可增加百分之一转变成每次可增加几秒或者几微秒的脉宽,查了好多资料大家都是占空比可调。
    发表于 06-05 10:13

    桌面RFID阅读器:现代资产管理的革命性工具?

    随着物联网技术的快速发展,桌面RFID阅读器正逐渐成为各行各业资产管理的重要组成部分。这种小巧而强大的设备不仅简化了数据读取和写入的过程,而且正在推动一场管理效率的革命。桌面RFID阅读器:小巧机身
    的头像 发表于 06-04 15:53 463次阅读
    桌面RFID<b class='flag-5'>阅读器</b>:现代资产管理的革命性工具?

    STM32CubeMX中使用F103ZET6怎么配置LCD?

    STM32CubeMX中使用F103ZET6怎么配置LCD
    发表于 06-04 10:22

    手搓了一个ESP32墨水屏阅读器,蛮简单的

    工程名称:3.7寸墨水屏阅读器时钟温湿度TXT/Epub前言帅小伙手搓ESP32墨水屏阅读器!可轻松续航100天!01阅读器功能/亮点?1.具有阅读器、时钟、温湿度监测、闹钟、番茄钟功
    的头像 发表于 05-27 08:04 331次阅读
    手搓了一个ESP32墨水屏<b class='flag-5'>阅读器</b>,蛮简单的

    学STM32为什么首选F103C8T6

    STM32F103C8T6是STMicroelectronics(ST)推出的一款基于ARMCortex-M3内核的32位微控制单片机(MCU)。它具有一系列优势和广泛的应用场景。这里就来介绍一下
    的头像 发表于 05-18 08:04 5699次阅读
    学STM32为什么首选<b class='flag-5'>F103C8T6</b>?

    如何用stm32f103zet6控制伺服电机的加减速运动?

    想用stm32f103zet6控制伺服电机的加减速运动,不知该怎么做,请大神指教。具体情况是有个机械凸轮,有一根顶杆顶着凸轮边沿,随着凸轮转动及其外径的变化,水平顶杆会沿水平方向作直线运动。现在可以
    发表于 04-30 08:00

    STM32F103ZET6的SPI通讯时,从机的SPI时钟被干扰导致接收的数据错位要如何恢复?

    在使用STM32F103ZET6的SPI通讯时,通讯时从机的SPI时钟被干扰导致接收的数据错位,且无法恢复,只有重新复位,接收的数据才能恢复,程序是基于HAL库写的。大家知道有什么办法可以让被干扰的时钟恢复正常吗?
    发表于 04-25 07:42

    为什么我程序都能下载到stm32f103zet6上,却不能下载到stm32f103c8上?

    为何我程序都能下载到stm32f103zet6上,却不能下载到stm32f103c8上?需要改变什么配置吗?
    发表于 04-17 06:42

    stm32f103zet6换为stm32f103zct6后编译不通过是怎么回事?

    芯片类型更换:stm32f103zet6 换为 stm32f103zct6, 编译不通过
    发表于 04-02 07:33

    用Cubemx生成的F103ZET6 USB Audio Device Class的代码,能正常被枚举但没有声音怎么解决?

    用Cubemx 生成的F103ZET6 USB Audio Device Class的代码,能正常被枚举,但没有声音,用Cubemx 生成的F407ZGT6 USB Audio Device
    发表于 03-18 06:21

    请问在f103C8T6中外设的引脚转接到ZET6上的时候是否需要更换引脚?

    如题,请问在f103C8T6中外设的引脚转接到ZET6上的时候是否需要更换引脚,还是已经设置好的可以直接转接上去用
    发表于 03-14 06:37

    STM32F103ZET6与ADXL362使用SPI通信时出现了读取不出数据的问题怎么解决?

    工程师们好,我在使用STM32F103ZET6与ADXL362使用SPI通信时出现了读取不出数据的问题,因此想请教一下。我采用在读取PART_ID时可以正常读出数据,在读取XYZ三轴数据时读取不出
    发表于 01-01 08:16