资讯详情

硬件课程设计:基于STM32的多功能播放器之小说阅读

在TFTLCD屏幕上显示的中英文文本文件是本次硬件课程设计的基本要求,也是我设计的多功能播放器最重要的功能,需要提前阅读和存储SD分析后,卡中的文本文件不仅可以显示中英文字符,还可以避免中文乱码。同时,模拟小说阅读器实现文本文件的翻页功能,用键1和键2切换页面,键3返回目录。

SPI是串行外设界面(Serial Peripheral Interface)的缩写,是 Motorola 公司推出的同步串行接口技术是高速、全双工、同步通信总线。具有通信简单、数据传输速率快以及穿双工通信等优点。但数据可靠性存在一些缺陷,因为没有指定的流量控制和响应机制来确认是否收到数据。

SPI由于接口相对简单,应用广泛,主要用于 EEPROM,FLASH,实时时钟,AD数字信号处理器和数字信号解码器之间的转换器。即一个SPI的Master通过SPI与一个从设备,即上述的那些Flash,ADC等,进行通信。保证主从设备之间的时钟SCLK在一致条件下,即保证主从设备时间的一致性,主从设备之间就可以完成SPI正常通讯。

SPI通常作为单片机外部芯片串行扩展接口,以主从方式工作。这种模式通常有一个主设备和一个或多个从设备,通常需要四条接口线(单向传输时可以有三条线)。所有基于SPI所有设备均包括4个引脚:MISO(数据输入),MOSI(数据输出),SCLK(时钟)、CS(片选)。

1)MOSI:从设备数据输入主设备数据输出;

2)MISO:从设备数据输出输入主设备数据;

3)SCLK:由主设备产生的时钟信号;

4)CS/SS:从设备使能信号由主设备控制。当有多个设备时,当我们的主设备和某个设备通信时,因为每个设备都有一个选择引脚连接到主设备机器 将从设备对应的片选引脚电平拉低或拉高。

SPI通信有四种不同的模式,不同的设备可能在工厂配置到某种模式,这是不能改变的;但我们的通信双方必须在同一模式下工作,所以我们可以处理我们的主要设备SPI配置模式,通过CPOL(Clock Polarity 时钟极性)和CPHA(Clock Phase 时钟相位)控制我们主要设备的通信模式如下:

Mode0:CPOL=0,CPHA=0;Mode1:CPOL=0,CPHA=1

Mode2:CPOL=1,CPHA=0;Mode3:CPOL=1,CPHA=1

时钟极性CPOL是用来配置SCLK当电平处于空闲或有效状态时,时钟相位CPHA数据采样是用来配置在几个边缘的:

CPOL=0,表示当SCLK=0点处于空闲状态,所以有效状态是SCLK在高电平时

CPOL=1,表示当SCLK=1点处于空闲状态,所以有效状态是SCLK低电平时

CPHA=0,数据采样在第一个边缘,数据发送在第二个边缘

CPHA=1.数据采样在第二个边缘,数据发送在第一个边缘

void SPI1_Init(void)  {      //SPI和GPIO结构体     SPI_InitTypeDef SPI_InitStructure;     GPIO_InitTypeDef GPIO_InitStructure;  //SPI和GPIO时钟使能     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );  //配置SCLK、MOSI和MISO三个GPIO引脚     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //拉高片选线
    GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);

    //设置数据模式
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;   //设置工作模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  //设置数据大小

    //设置通信模式
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS由软件管理
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//分频值
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //数据高位先传
    SPI_InitStructure.SPI_CRCPolynomial = 7;  //CRC值计算多项式
    SPI_Init(SPI1, &SPI_InitStructure);  

    //使能SPI   
    SPI_Cmd(SPI1, ENABLE);   
}  

SPI配置步骤如下:

1)使能SPI和GPIO时钟

2)配置SCLK、MOSI和MISO三个GPIO引脚

3)拉高片选线

4)配置SPI控制寄存器 

5)使能SPI

1)W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128Mbit,相当于 16M 字节。W25Q128 可以支持 SPI 的模式 0 和模式 3,也就是 CPOL=0/CPHA=0 和CPOL=1/CPHA=1 这两种模式。

2)写入数据时,需要注意以下两个重要问题:

①Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,W25Q128的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。

②Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。因此通常要改写某部分空间的数据,必须首先进行一定物理存储空间擦除,最小的擦除空间,通常称之为扇区,扇区擦除就是将这整个扇区每个字节全部变成 0xFF。每款 Flash 的扇区大小不一定相同,W25Q128 的一个扇区是 4096 字节。为了提高擦除效率,使用不同的擦除指令还可以一次性进行 32K(8 个扇区)、64K(16 个扇区)以及整片擦除。

3)W25Q128 内部有一个“SPI Command & Control Logic”,可以通过 SPI 接口向其发送指令,从而执行相应操作。指令的长度是不定的,有单字节的,也有多字节的,W25Qxx 一共具有 34 个操作指令,在此只列举常用的 12 个。

           

void W25qx_Init(void);

W25Q128初始化函数,完成SPI的配置,检查中文字库,以及读取芯片型号以确定通讯正常。

void W25qxx_ReadBuffer ( u32 addr, u8 *p, u16 num);

从W25Q128中读取数据到指定内存区域。

u32 addr    读取地址

u8 *p       读出的数值存放位置

u16 num     连续读取的字节数  

void W25qxx_WriteBuffer( u32 addr, u8 *p, u16 num);  

向W25Q128中写数据,写之前查出该区域数据,同时防止向字库存储区域写数据

u32 addr     写入地址

u8 *p        要写入的数据存储器

u16 num      连续写入的字节数

        SD存储卡(Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD存储卡的技术是从MMC卡(MultiMedia Card格式上发展而来,在兼容SD存储卡基础上发展了SDIO(SD Input/ Output)卡,此兼容性包括机械,电子,电力,信号和软件,通常将SD、SDIO卡俗称SD存储卡。

        一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5 个部分。存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器。

        SD卡有两种驱动模式:SPI模式与SDIO模式。它们所使用的接口信号是不同的。在SPI模式下,只会用到SD卡的4根信号线,即CS、DI、SCLK与DO(分别是SD卡的片选、数据输入、时钟与数据输出)。

        SD卡共支持三种传输模式:SPI模式(独立序列输入和序列输出),1位SD模式(独立指令和数据通道,独有的传输格式),4位SD模式(使用额外的针脚以及某些重新设置的针脚。支持四位宽的并行传输)。

u8 SD_Initialize(void)

初始化SD卡

u8 SD_ReadDisk(u8 *buf,u32 sector,u8 cnt)

读SD卡

U8 *buf     读出的数据在内存中的缓存区

U32 sector  开始读的扇区位置

U8 cnt      连续读取的扇区数

u8 SD_WriteDisk(u8 *buf,u32 sector,u8 cnt)

写SD卡

u8 *buf     写入数据在内存中的缓存区

u32 sector  开始写入的扇区位置

u8 cnt      连续写入的扇区数

        TFT-LCD(thin film transistor-liquid crystal display)即薄膜晶体管液晶显示器。液晶显示屏的每一个像素上都设置有一个薄膜晶体管(TFT),每个像素都可以通过点脉冲直接控制,因而每个节点都相对独立,并可以连续控制,不仅提高了显示屏的反应速度,同时可以精确控制显示色阶,所以TFT液晶的色彩更真,因此TFT-LCD也被叫做真彩液晶显示器。

        常用的TFT液晶屏接口有8位、9位、16位、18位,这里的位数表示的是彩屏数据线的数量。常用的通信模式有6800模式和8080模式,其中8080模式的接口有5条基本的控制线和多条数据线(8/9/16/18位),它们的功能如下表:

           

         本此实验采用的是1.8寸TFT LCD液晶屏,驱动芯片为ST7735,SPI接口的引脚有8脚,其中GND和VCC是液晶的电源引脚,VCC接3.3V。SCL和SDA分别为SPI的时钟信号线和数据线。RES为LCD的复位信号,可以有STM32控制其复位。DC为数据/命令选择端,低电平写命令,高电平写数据。CS为液晶屏片选信号,低电平使能;BL为背光信号,低电平关闭背光。

void LCD_Init(void)

初始化LCD

void LCD_Fill(u16 sx, u16 sy, u16 ex, u16 ey, u16 color)

在指定区域内填充单个颜色

u16 sx, u16 sy     左上角起始坐标

u16 ex, u16 ey     右下角结束坐标

u16 color          填充的颜色

void drawAscii(u16 x,u16 y,u8 num,u8 size,u32 fColor, u32 bColor)

在指定位置显示一个字符

u16 x, u16 y     起始坐标

u8 num           要显示的字符,从‘ ’到‘~’

u8 size          字体大小 12/16/24/32

u32 fColor       字体颜色

u32 bColor       背景颜色

void LCD_String(u16 x, u16 y, char* pFont, u8 size, u32 fColor, u32 bColor)

在屏幕上显示字符串,支持中英文

u16 x, u16 y     起始坐标

char *pFont      字符串储存位置

u8 size          字体大小

u32 fColor       字体颜色

u32 bColor       背景颜色

void LCD_Num(u16 x, u16 y, u16 num, u8 size, u32 fColor, u32 bColor)

在屏幕上显示数值

u16 x, u16 y     起始坐标

u16 num          要显示的数值大小,0~999

u8 size          字体大小

u32 fColor       字体颜色

u32 bColor       背景颜色

void LCD_Image(u16 x, u16 y, u16 width, u16 height, const u8 *image)

在指定区域填充指定图片数据

u16 x, u16 y     起始坐标

u16 width        图片宽度

u16 height       图片高度

const u8 *image  数据缓存地址

        适合嵌入式小型单片机,是一个独立的软件层文件系统,我们只需要将底层硬件的读取函数移植到FATFS提供的向下的接口(Media Access Interface),完成之后,就可以像电脑一样使用文件的操作函数(FATFS提供的向上的供我们使用的API函数)。

        FATFS模块的层次结构如下图示:

                     

最顶层是应用层:使用者只需要调用FATFS模块提供给用户的一系列应用接口函数(如f_open, f_read, f_write和f_close等),就可以像在PC上读写文件那样简单。

中间层FATFS模块:实现了FAT文件读写协议;它提供了ff.c和ff.h文件,一般情况下不用修改,使用时将头文件包含进去即可。

最底层是FATFS模块的底层接口:包括存储媒介读写接口和供给文件创建修改时间的实时时钟,需要在移植时编写对应的代码。

1)修改diskio.c文件(与硬件相关的底层驱动)

2)修改ffconf.c文件(修改相关的宏)

3)格式化文件系统(如果已有文件系统可以不用格式化)

        汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。只要知道了一个汉字点阵的生成方法,那么我们在程序里面就可以把这个点阵数据解析成一个汉字。知道显示了一个汉字,就可以推及整个汉字库了。

        常用的汉字内码系统有 GB2312,GB13000,GBK,BIG5(繁体)等几种,其中 GB2312支持的汉字仅有几千个,很多时候不够用,而 GBK 内码不仅完全兼容 GB2312,还支持了繁体字,总汉字数有 2 万多个,完全能满足我们一般应用的要求。

        汉字在各种文件里面是以内码的形式存储的,每个汉字对应着一个内码,在知道了内码之后再去字库里面查找这个汉字的点阵数据,然后在液晶上显示出来。我们要解决的最大问题就是制作一个与汉字内码对得上号的汉字点阵库,而且要方便单片机的查找。

        每个 GBK 码由 2 个字节组成,第一个字节为 0X81~0XFE,第二个字节分为两部分,一是 0X40~0X7E,二是 0X80~0XFE。我们把第一个字节代表的意义称为区,那么 GBK 里面总共有 126 个区(0XFE-0X81+1),每个区内有 190 个汉字(0XFE-0X80+0X7E-0X40+2),总共就有 126*190=23940 个汉字。我们的点阵库只要按照这个编码规则从 0X8140 开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数*190。这样,我们就可以得到在这个字库里面定位汉字的方法:

                         GBKL<0X7F :Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;

                         GBKL>0X80 :Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;

        其中 GBKH、GBKL 分别代表 GBK 的第一个字节和第二个字节(也就是高位和低位),Hp为对应汉字点阵数据在字库里面的起始地址(假设是从 0 开始存放),csize 代表一个汉字点阵所占的字节数。

        这样我们只要得到了汉字的 GBK 码,就可以得到该汉字点阵在点阵库里面的位置,从而获取其点阵数据,显示这个汉字了。

       万事开头难,这部分工作是我在整个工程中花费时间最多,也是感觉最难的一部分工作。初次接触单片机,一切都感觉很陌生,好在有微机原理和操作系统的理论知识,对计算机系统有比较清晰的了解,并且很好地完成了微机实验中FPGA的设计工作,有了上述基础,再通过B站上正点原子的有关stm32教学视频的学习,我初步掌握了stm32系统架构,以及像系统时钟、中断、SPI、USART和定时器等基本操作,为下一步的学习打下坚实的基础。

        初步学习了stm32后,我开始逐步移植W25Q128、SD卡和TFTLCD的驱动程序,移植后对主要的驱动函数进行测试,测试成功后再结合各模块的技术手册逐行读懂每一行代码,在读完整个驱动程序后,虽然对模块内部具体构造还不甚了了,但已经能熟练掌握模块的运用,并在此基础上开发出更多相关的功能。

        不带文件系统的SD卡仅能实现简单的读写扇区操作,要真正应用SD卡,并在此基础上开发出多种多样的应用功能,就必须要使用文件系统。文件系统只要了解其基本的工作原理,移植起来就会比较顺利,配置后要对其进行多样测试,以检验文件系统是否移植成功。

        在文本文件中,英文字符用一个字节存储,而中文字符是用两个字节存储,当读取文本文件的一定字节数时,读取的最后一个字节恰好是中文字符的第一个字节,就会造成后面的文件乱码。

        英文字符编码是0~127,而中文字符第一个字节编码是128~255,所以可以通过顺序检查字节的编码从而区分中英文字符,检测到读取的最后一个字节是中文的第一个字节时,必须少读或者多读一个字节数,这样就解决了中英文乱码问题。

u8 Chinese2Char(u8 * buff, u8 size) //检测字符串最后一个字节是否是中文第一个字节,
{                                   //是返回0,不是返回1
    u8 i = 0;
    while(i != size - 1 && i != size -2) //顺序检测,直到最后剩下一个或两个字节
    {
        if(*buff < 128)  {buff++;i++;}
        else  {buff += 2;i += 2;}
    }
    if(i == size - 2)      //剩下最后两个字节
    {
        if(*buff > 128)  return 0;  
        else
        {
            buff++;
            if(*buff > 128)  return 1;
            else  return 0;
        }
    }
    if(i == size - 1)  //剩下最后一个字节
    {
        if(*buff > 128)  return 1;
        else  return 0;
    }
    return 0;
}

u8 *buff     要检测的字符串储存位置

u8 size      字符串大小

void reader()   //小说阅读
{
    vKey_Close();                                        //关闭按键中断      
    LCD_String(1, 40, "宰执天下", 32, YELLOW, BLACK);     //显示小说信息一定时间
    LCD_String(16, 85, "作者:cuslaa", 16, YELLOW, BLACK);
    delay_ms(2000);
    f_open(&file0, "1.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
    rbuff[PAGE_SIZE] = '\0';
    flag2 = 2;
    vKey_Init();                                          //开中断
    while(flag1 == 1)     //按键3没有按下
    {
        delay_ms(200);
        if(flag2 == 1)    //按键1按下
        {
            if(ye > 1)
            {
                ye--;
                count -= page[ye];
                f_lseek(&file0, count - page[ye - 1]);
                f_read(&file0, rbuff, page[ye - 1], &num_read);
                if(page[ye - 1] == PAGE_SIZE - 1)
                rbuff[PAGE_SIZE - 1] = '\0';
            }
        }
        if(flag2 == 2)     //按键2按下
        {
            if(f_size(&file0) - count > PAGE_SIZE)
            {
                f_read(&file0, rbuff, PAGE_SIZE, &num_read);
                if(Chinese2Char((u8 *)rbuff, PAGE_SIZE))    //检测是否乱码
                {
                    count += PAGE_SIZE - 1;
                    f_lseek(&file0, count);
                    rbuff[PAGE_SIZE - 1] = '\0';
                    page[ye] = PAGE_SIZE - 1;
                } else
                {
                    count += PAGE_SIZE;
                    page[ye] = PAGE_SIZE;
                }
                ye++;
            } else if(f_size(&file0) - count > 0)
            {
                f_read(&file0, rbuff, PAGE_SIZE, &num_read);
                page[ye] = f_size(&file0) - count;
                count = f_size(&file0);
                ye++;
            }
        }
        if(flag2 != 0)    //按键1、2都没有按下时就不重复显示,避免闪烁
        {
            LCD_Fill(1, 1, 129, 160, BLACK);
            LCD_String(6, 10, rbuff, 16, YELLOW, BLACK);
            sprintf(yebuff, "第%d页³", ye);
            LCD_String(45, 145, yebuff, 12, YELLOW, BLACK);
        }
        flag2 = 0;
        if(flag1 == 0)   //按键3按下,关闭文件
            f_close(&file0);
    }
}

u8 flag1                按键3按下置0,没有按下置1

u8 falg2                按键1按下置1,按键2按下置2

#define PAGE_SIZE       宏定义,每一页固定字节数,方便修改

u8 page[100]            存放每一页字节数,乱码时比PAGE_SIZE少1个

char rbuff[PAGE_SIZE + 1]  读取的数据

u8 ye                  页数

LCD上显示英文字符较快,但显示中文字符较慢

英文字库烧录到内部Flash中,读取速度快,中文字库存放在外部Flash中,通过SPI通信,读取速度慢,在将SPI三个从机的波特率设置为最大,即时钟的二分频后,中文字符的显示仍然较慢,暂时得不到更好的解决方法,除非更换主频更高的开发板

硬件课程设计:基于STM32的多功能播放器-单片机文档类资源-CSDN下载

标签: 2sd965晶体管

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台