STM32
直播中

辛太励

8年用户 1206经验值
擅长:20153
私信 关注
[问答]

如何通过串口将字库文件下载到flash中?

如何通过串口将字库文件下载到flash中?

回帖(1)

王君凯

2021-12-8 14:46:29
1. 首先配置好串口
2. 配置好spi
3. 配置好flas
4. 准备好字库文件和串口助手
5. 开始下载     
1.   Flash芯片   

我这里使用的是W25Q64flash芯片大小为8M(Byte).     
Flash 芯片存储区域划分:8MB分为128块,每块大小为64KB;每块又分为16个扇区,每个扇区大小为4KB;每个扇区有16页,每页大小为256个字节 。
  1.1 芯片引脚图






1.CS  片选信号,低电平有效,操作flash之前要先拉低CS,这里要注意每执行一条新指令之前必须要使CS管脚有个下降沿信号。   
2.DODI 分别为串行数据的输出输入管脚,   
3.CLK 是串行通讯的(SPI)的时钟信号线。SPI是同步串行通信协议,所谓同步就是靠这个时钟信号,,一个脉冲接收和发送一位   
4.WP  是写保护管脚,低电平有效此时只能读数据,不能执行写入数据;高电平时可读可写     
5.HOLD  为保持管脚,低电平有效。   
1.2 Flash常用指令:   

/*----------W25Q64常用操作命令----------*/
#define WRITE_ENADLED 0x06         //写使能
#define WRITE_DISABLE 0x04         //写禁能
#define READ_STATUS 0x05           //读状态寄存器1
#define READ_STATUS2 0x35          //读状态寄存器2
#define READ_DATA 0x03             //读数据
#define WRITE_STATUS 0x01          //写状态寄存器
#define WRITE_DATA 0x02            //页编程
#define BLOCK_ERASE 0xD8           //块擦除(64K)
#define HALF_BLOCK_ERASE 0x52      //半块擦除(32K)
#define SECTOR_ERASE 0x20          //扇区擦除(4K)
#define CHIP_ERASE 0xC7            //芯片擦除
#define CHIP_POWER_DOWN 0xB9       //芯片掉电
#define CHIP_RPOWER 0xAB           //释放掉电/器件ID
#define READ_ID 0x90               //制造/器件ID
#define READ_JEDEC_ID 0x9F         //JEDEC ID





1. 读取数据时,读取完一个字节后,Flash会自动将地址加一,只要一直给信号,就可以连续读取数据。
2. 写人数据时,是按页来进行写入的,写完一个字节Flash也会自动加一,直到页尾,然后地址会自动眺到当前页的首地址(如果这时候不停止写入数据就会覆盖之前的数据)。也就是说一次最多只能写入一页数据(256字节),写完一页数据要重新向Flash写入地址(下一页的首地址)   
1.3. Flash中的两个寄存器











状态寄存器(S0-S7),用来控制写使/禁能.SPI配置.监视Flash是否忙碌和数据保护功能。
1.3.1  BUSY位 :   
     BUSY位是状态寄存器的(SO)位,是一个只读位,当设备执行页程序、扇区擦除、块擦除、芯片擦除或写状态寄存器指令时,它被置1。在此期间,设备将忽略除读状态寄存器和擦除暂停之外的指令。当页写入. 擦除或写状态寄存器指令已经完成,BUSY位将被清0,表示设备准备好执行下一条指令。
1.3.2   写使能保护位 WEL:
    WEL是状态寄存器的(S1)位是一个只读位,在执行了写使能指令后被置1。当设备为写禁能时,WEL状态位被清0。一般写禁能状态发生在开机或在下列任何指令之后:写禁用,页程序,扇区擦除,块擦除,芯片擦除和写状态寄存器。
1.3.3  高速SPI模式 QE:   
    QE是状态寄存器的(S9)位,是可读/写位。该位默认为0,SPI为两线制,可以使用标准SPI和快速SPI模式;当QE置1时,SPI为四线制,WP 和/Hold将被禁用而是变为IO2和IO3.也就是启用IO2和IO3.
剩下的几个位我也没用过。。。也不常用。。。我这里主要是写入字库代码部分,硬件部分的资料可以百度和文章开头下载手册。我这里就不重复造轮子了,也没有别人写的好!
1.4.Flash SPI操作

  1.4.1 标准SPI   
    W25Q64通过一个SPI兼容的总线进行访问,该总线由四个信号组成:串行时钟(CLK)、芯片选择(/CS)、串行数据输入(DI)和串行数据输出(DO)。标准SPI指令使用Dl引脚输入指令、地址或数据,在CLK上升沿时采集数据。DO引脚是用来读取数据或状态,CLK下降沿时采集数据。   
    支持标准SPI的模式0和模式3,模式0和模式3的主要区别是时钟信号CLK在正常状态下(SPI主机在待机或是没有传输数据时片选信号CS没有拉低时)的电平信号不一样。使用模式0要求在CS上升沿和下降沿时CLK为低电平;模式3为CS在上升沿和下降沿时CLK为高电平

2.  SPI配置

2.1 SPI介绍

先简单说一下SPI通信,与串口和 I2C一样都是一种通讯手段,只要配置正确这些通讯方式用起来都大同小异。
  特点:同步通信   总线形式    全双工   没有应答位   通信速率高  使用方便简单   主从模式
1. spi 通讯与其说它是全双工通讯不如说它是同步数据交换更准确。
2. 标准spi是四根线,分别是:SDO,SDI,SCK和 CS。
3. CS为片选信号,低电平有效。
4. SDI SDO 分别是数据输入和数据输出管脚。
5. SCLK是时钟信号。
6. spi通讯中只允许有一台主设备,与多个从设备通讯。
7. spi需要时钟信号才能工作,但从设备是没有独立的时钟,从设备的时钟信号是由主设备来控制的
8. 我们大多是使用spi控制器进行相互通讯,所以我们只需要配置好spi控制器的寄存器就可以使用了
9. spi通讯时实际上是主设备上的移位寄存器与从设备的移位寄存器在做数据交换
10. 所以发送数据必然会收到数据,接收数据必然要发送数据,
11. spi有四种工作模式,分别是模式0,模式1,模式2,模式3
12. spi的四种模式是由 极性CPOL  相位CPHA来决定的。
13. 极性CPOL 是指时钟信号SCK在空闲时刻(就是不发送数据时或是发送完数据的时候)的电平状态,
        CPOL=0:SCLK空闲时为低电平,SCK有效时为高电平。
        CPOL=1:SCLK空闲时为高电平,SCK有效时为低电平。
14. 相位CPHA 是指在SCLK的第几个边缘进行信号采集
        CPHA=0:是在SCLK的第一个边缘进行数据采集;如果CPOL=0 就是上升缘 CPOL=1则为下降缘
        CPHA=1:是在SCLK的第二个边缘进行数据采集;如果CPOL=0 就是下降缘 CPOL=1则为上升缘
15. 模式0:CPOL=0  CPHA=0
      模式0:CPOL=0  CPHA=1
      模式0:CPOL=1  CPHA=0
      模式0:CPOL=1  CPHA=1
  



   SPI通讯  



   SPI时序 2.2 FLASH的SPI配置代码


#include "SPI.h"

void SPI2_Init()
{
        GPIO_InitTypeDef GPIO_InitStruct;
        SPI_InitTypeDef SPI_InitStruct;

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);      //打开GPIO时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);        //打开Spi时钟

        //配置GPIO为复用推挽输出
        GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOB,&GPIO_InitStruct);
   
        //配置SPI模块的参数
        SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;      //配置SPI的传输模式;设置为全双工模式
        SPI_InitStruct.SPI_Mode = SPI_Mode_Master;                           //配置SPI的工作模式;配置为主机模式
        SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;                       //配置传输数据宽带;设置为8bit模式
        SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;                              //CPOL极性 配置SPI空闲时钟的电平;配置为高电平。CPOL和CPHA的极性设要和从设备的极性一致
        SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;                            //CPHA相位 配置SPI在时钟的第一个或第二个边缘采集数据;配置成第二个边缘
        SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;                               //SPI的片选管脚是硬件控制(SPI模块外部控制)还是软件控制(GPIO控制);配置成软件控制
        SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;     //预设值SPI的时钟分频;配置成4分频,SPI外设最快为18MHz;APB1总线最大时钟频率为26MHz
        SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;                      //数据位的发送顺序,即先发送数据的高位(MSB)还是低位(LSB);设置为高位
        SPI_InitStruct.SPI_CRCPolynomial = 7;                                //指定用于CRC计算的多项式
        SPI_Init(SPI2, &SPI_InitStruct);                                     //根据SPI_InitStruct中配置的参数初始化SPI外设寄存器
        SPI_Cmd(SPI2,ENABLE);                                                //使能SPI外设

}


uint8_t SPI2_Sed_RecData(uint8_t dat){
//通过SPI2发送数据
        while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {  //检查SPI2的发送状态位(TXE)是否SET(表示空闲可以发送)RESET 表示忙碌
        }
                        SPI_I2S_SendData(SPI2, dat);                               //发送数据库函数


//通过SPI2读取数据
        while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET) {   //检查SPI2的接收状态位(RXNE)是否为SET(表示可接收)RESET 表示忙碌
        }
                        return SPI_I2S_ReceiveData(SPI2);                        //接收数据库函数
}
3 字库下载流程

1.首先我们配置串口,并打开接收中断。
2.定义两个标志位
    __IO uint8_t FLAG_FlowData   数据流模式标识  表示串口接下来接收的全部是字库数据 由手动置位和清零
    __IO uint8_t FLAG_StartFlowData    数据流模式开始/结束传输标识  由串口接收中断函数置位,数据帧判断函数清零
3。接收缓存
     __IO uint8_t CocheData[64]  串口接收缓存区
     __IO uint8_t count=0;   接收缓冲区计, 数下载字库时用来监视接收缓冲区是否满了,命令模式时用来记录数据帧的字节数















下载字库之前要确保所下载的区域已经擦除过了,一般如果是做实验可以直接格式化,指令模式里有格式化指令。但没有写多块擦除指令和多半块擦除指令,指令列表里的都是单个块擦除指令。
所有输入地址或是输入数字的操作都要清空串口助手的发送区并把“16进制发送勾上”。
如果要用指令模式来测试字库:在LCD显示汉字,需要在源文件 text.c 中修改一下

GBK = ((uint32_t)190 * GBK_H + GBK_L) * CN_PYL + (uint32_t)CH_PYL * 96;      //计算所打印的字符在Flash字库存储的位置

GBK = (GBK_H - 0x20)* CH_PYL;                       //计算所打印的字符在Flash字库存储的位置


在源文件text.c 开头找到 宏 #define XZDZ 0   将0改成你的下载地址
因为我的下载地址是0
/计算所打印的字符在Flash字库存储的位置在源文件text.c 开头找到 宏 #define XZDZ 0   将0改成你的下载地址因为我的下载地址是0 操作流程






1.先发送指令 flash -f     格式化flash
2.发送指令  flash -w      向flash写入数据
3.清空串口助手的发送区并把“16进制发送勾上”





4.输入24未地址      我的ASCII字库下载地址是0x00000000,汉字下载地址是0x00000600(我这里用的是8*16的ASCII字体,16*16的汉字字体GBK字库)
下载地址的计算:先打开ASCII字库文件属性,查看文件大小有多少个字节,然后用计算器的程序员模式转换成16进制,我是从地址0x00000000开始下载的ASCII,所以汉字下载地址就是0x00000000+ASCII字库的大小









5.点击打开文件,下载要下载的字库文件
6.等待下载完成











4. 代码部分
4.1  FLASH
这里只有部分代码,主要是核心部分。


W25Q64Flash.c


#include "W25Q64Flash.h"
extern __IO uint8_t FLAG_StartFlowData; //数据流模式开始/结束传输标识
extern __IO uint8_t FLAG_FLASH_WRITE;   //flash写入标识 1表示可以写入一个字节,由flash写入函数清零串口中断置位
extern __IO uint8_t count;              //接收计数


/*----------初始化函数----------*/
void W25Q64FlashInit() {

        GPIO_InitTypeDef GPIO_InitStruct;

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);      //打开GPIO时钟

        //配置GPIOB.12(SPI2的片选引脚 NSS)为推挽输出
        GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_12);
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_Init(GPIOB, &GPIO_InitStruct);
   
        CS_OFF;
        SPI2_Init();    //初始化SPI2
}

/*----------W25Q644写使能函数----------*/
void Write_EN(){
        CS_ON;
        SPI2_Sed_RecData(WRITE_ENADLED);
                CS_OFF;
}

/*----------W25Q644写禁能函数----------*/
void Write_NOEN() {
        CS_ON;
        SPI2_Sed_RecData(WRITE_DISABLE);
                CS_OFF;
}

/*----------判断Flash是否繁忙----------*/
void FLASH_Busy() {
        CS_ON;
        SPI2_Sed_RecData(READ_STATUS);
        while ((SPI2_Sed_RecData(ZERO_DATA)&STATUS_BUSY) == SET);
        CS_OFF;
}

/*----------读取状态寄存器----------*/
uint16_t FLASH_ReadSR() {
        uint16_t dat = 0;                   //临时存储器
       
        CS_ON;                              //开片选
        SPI2_Sed_RecData(READ_STATUS2);     //读取状态寄存器2
        dat = SPI2_Sed_RecData(ZERO_DATA);
        CS_OFF;                             //关片选
       
        dat = dat << 8;                     //把数据往后位移8位,因为状态寄存器2在高8位
       
        CS_ON;
        SPI2_Sed_RecData(READ_STATUS);      //读取状态寄存器1
        dat |= SPI2_Sed_RecData(ZERO_DATA);       //状态寄存器1在低8位
        CS_OFF;
       
        return dat;
}

/*----------写状态寄存器----------*/
void FLASH_WriteSR(uint16_t sr) {
        FLASH_Busy();                        //判断Flash是否繁忙,繁忙则等待
       
        Write_EN();                          //写使能
       
        CS_ON;
        SPI2_Sed_RecData(WRITE_STATUS);      //发送写状态寄存器指令
       
        SPI2_Sed_RecData(sr&0xff);           //先写入第一个字节
  SPI2_Sed_RecData((sr>>8)&0xff);        //写入第二个字节
       
        CS_OFF;
       
        FLASH_Busy();
}

/*----------读取指定个数的数据----------*/
void FLASH_Read_Data(uint8_t *dat, uint32_t add, uint32_t len){
        FLASH_Busy();
         
        CS_ON;
         
        SPI2_Sed_RecData(READ_DATA);             //发送读取数据指令
          
        SPI2_Sed_RecData((add>>16)&0xff);        //发送24位地址 高位在前低位在后
        SPI2_Sed_RecData((add>>8)&0xff);
        SPI2_Sed_RecData(add&0xff);
       
        while(len--){
          *dat++=SPI2_Sed_RecData(ZERO_DATA);     //把读取的数据存储到指定的数组里
        }
       
        CS_OFF;
}

/*----------写入指定个数的数据----------*/
void FLASH_Write_Data(uint8_t *dat, uint32_t add, uint16_t len){
  FLASH_Busy();
       
        Write_EN();                              //写使能
       
        CS_ON;
         
        SPI2_Sed_RecData(WRITE_DATA);            //发送写数据指令
          
        SPI2_Sed_RecData((add>>16)&0xff);        //发送24位地址
        SPI2_Sed_RecData((add>>8)&0xff);
        SPI2_Sed_RecData(add&0xff);
       
        while(len--){
          SPI2_Sed_RecData(*dat++);               //把指定数据里的数据写入flash
        }
       
        CS_OFF;
       
        FLASH_Busy();
}

/*----------连续数的数据写入----------*/
void FLASH_SWrite_Data(uint8_t *dat, uint32_t add, uint32_t len) {
        uint16_t len2 = 256 - (add % 256);       //当前地址所在页剩余写入字节数
       
        while (len >= len2) {                    //第一次判断写入字节数是否大于页剩余写入字节数
                FLASH_Write_Data(dat, add, len2);    //先写入页剩余的字节数
                add = add + len2;                    //重新计算写入地址
                dat = dat + len2;                    //重新计算数据地址
                len = len - len2;                    //重新计算写入字节数
                len2 = 256;                          //用于第二次判断剩余写入字节数是否大于256
                TD_Soft_us(400);                     //延时400us
        }
        if (len != 0) {
                FLASH_Write_Data(dat, add, len);         //写入剩下的字节
        }
}

/*----------格式化flash----------*/
void FLASH_Format(){
  FLASH_Busy();
       
        Write_EN();            
       
        CS_ON;
        SPI2_Sed_RecData(CHIP_ERASE);   //发送格式化指令 然后立即拉高片选,否则指令无效
        CS_OFF;
       
}

/*----------扇区擦除(4K)----------*/
void FLASH_Sector_Erase(uint32_t sector){
  uint32_t add=sector*4096;               //要擦除的扇区地址
       
        FLASH_Busy();
       
        Write_EN();
       
        CS_ON;
        SPI2_Sed_RecData(SECTOR_ERASE);        //发送扇区擦除指令
       
        SPI2_Sed_RecData((add>>16)&0xff);
        SPI2_Sed_RecData((add>>8)&0xff);
        SPI2_Sed_RecData(add&0xff);
       
        CS_OFF;
}

/*----------块擦除(64K)----------*/
void FLASH_Block_Erase(uint32_t block){
  uint32_t add=block*4096*16;
       
        FLASH_Busy();
       
        Write_EN();
       
        CS_ON;
        SPI2_Sed_RecData(BLOCK_ERASE);
       
        SPI2_Sed_RecData((add>>16)&0xff);
        SPI2_Sed_RecData((add>>8)&0xff);
        SPI2_Sed_RecData(add&0xff);
       
        CS_OFF;
}

/*----------串口数据流模式写入数据----------*/
void FLASH_ContReadData(uint32_t add) {
        uint16_t len2 = 256 - (add % 256);    //此地址所在页剩余写入字节数
        uint8_t len = len2 % 64;               //页剩余写入字节数除64的 余数
        uint8_t C_Data[64] = { 0 };           //临时数据存储

        while (FLAG_StartFlowData != 1);         //等待数据流模式开始
        /*消除与64不对齐的情况*/
        if (len) {                            //判断页剩余写入字节数是否与64对齐
                while ((count < len) && (FLAG_StartFlowData == 1));//
                len = CopyCocheData(C_Data, len);  将接收的数据拷贝到临时数据存储,并将接收计数清零
                FLASH_Write_Data(C_Data, add, len);
                if (FLAG_StartFlowData != 1) {    //数据流模式中断,或是数据量不足补齐64字节
                        return;
                }
                add = add + len;                  //重新计算地址
        }
        while (FLAG_StartFlowData == 1) {         //数据流模式进行中
                if (count >= 64) {                //一次向flash写入64个字节
                        len = CopyCocheData(C_Data, 64);//将接收的数据拷贝到临时数据存储,并将接收计数清零
                        FLASH_Write_Data(C_Data, add, len);
                        add = add + len;
                }
        }
        /*数据流模式结束后将剩下的数据写入flash*/
        len = CopyCocheData(C_Data, 64);
        if (len != 0) {
                FLASH_Write_Data(C_Data, add, len);         //写入剩下的字节
        }
}


/*----------读取芯片ID----------*/
void FLASH_ReadID(uint8_t *id){
  FLASH_Busy();
       
        CS_ON;
       
        SPI2_Sed_RecData(READ_ID);
          
        SPI2_Sed_RecData(0x00);
        SPI2_Sed_RecData(0x00);
        SPI2_Sed_RecData(0x00);
       
        *id++=SPI2_Sed_RecData(ZERO_DATA);  //芯片ID由2个字节组成
        *id=SPI2_Sed_RecData(ZERO_DATA);
       
        CS_OFF;
}
W25Q64Flash.h


#ifndef __W25Q64FLASH_H
#define __W25Q64FLASH_H
#include "stm32f10x.h"

/*-------------SPI2片选控制-------------*/
#define CS_OFF GPIO_SetBits(GPIOB,GPIO_Pin_12)      //关闭片选
#define CS_ON  GPIO_ResetBits(GPIOB,GPIO_Pin_12)    //打开片选

/*----------W25Q64常用操作命令----------*/
#define WRITE_ENADLED 0x06         //写使能
#define WRITE_DISABLE 0x04         //写禁能
#define READ_STATUS 0x05           //读状态寄存器1
#define READ_STATUS2 0x35          //读状态寄存器2
#define READ_DATA 0x03             //读数据
#define WRITE_STATUS 0x01          //写状态寄存器
#define WRITE_DATA 0x02            //页编程
#define BLOCK_ERASE 0xD8           //块擦除(64K)
#define HALF_BLOCK_ERASE 0x52      //半块擦除(32K)
#define SECTOR_ERASE 0x20          //扇区擦除(4K)
#define CHIP_ERASE 0xC7            //芯片擦除
#define CHIP_POWER_DOWN 0xB9       //芯片掉电
#define CHIP_RPOWER 0xAB           //释放掉电/器件ID
#define READ_ID 0x90               //制造/器件ID
#define READ_JEDEC_ID 0x9F         //JEDEC ID

/*----------状态寄存器说明----------*/
#define STATUS_BUSY 0x0001        /*总线忙标志位:为1时总线繁忙只能读取状态寄存器  
                                    0表示芯片器件可以接收其他的指令*/
#define STATUS_WEL 0x0002         /*写保护位:在执行完“写使能”指令后该位会被硬件自动置1
                                                                        当芯片掉电后和执行 写禁能、页编程、扇区擦除、块区擦除 以及 芯片擦除 指令都会进入“写保护状态”*/
#define STATUS_BP 0x001C          /*块区保护位:BP2、BP1、BP0这3位为可读可写位,分别在状态寄存器的S4、S3以及S2位。这3个位默认状态为0,
                                                                        即块区处于未保护状态。可以利用“写状态寄存器”指令对这几个位进行置1来达到块区保护的目的。块区保护状态为:没有保护、部分保护和全部保护状态。*/
#define STATUS_TB 0x0020         /*底部和顶部块保护位:默认值为0。可以利用“写状态寄存器”指令对这个位进行置1或清零。
                                                                        当TB = 0时,表示保护位从顶部开始,当TB = 1时,表示保护位从底部开始*/
#define STATUS_SEC 0x0040        /*扇区/块保护:SEC位为一个可读可写位,在状态寄存器的S6位,默认值为0。可以利用“写状态寄存器”指令对这个位进行置1或清零。当SEC = 0时,
                                                                        表示每次保护的区域大小为4K;当SEC = 1时,表示每次保护的区域大小为8K*/
#define STATUS_SRP 0x0180         /*状态寄存器保护位:SRP0和SRP1这2位为可读可写位,分别在状态寄存器的S7和S8(状态寄存器2)位。这两个位的默认值为0,
                                                                        可以利用“写状态寄存器”指令对这个位进行置1或清零。这2个位和读写保护管脚(/WP)决定了状态寄存器写保护的方式。状态寄存器写保护的方式有:软件保护,硬件保护、电源锁定或一次性可编程(OTP)保护*/
#define STATUS_QE 0x0200          /*快速SPI通讯使能:QE位为一个可读可写位,在状态寄存器的S9(状态寄存器2)位,默认值为0。以利用“写状态寄存器”指令对这个位进行置1或清零。
                                                                        当QE = 0时,W25Q54设置为标准速度模式或快速模式,保持管脚(/HOLE)和读写保护管脚(/WP)启用;当QE = 1时,W25Q54设置为高速模式,保存管脚(/HOLE)和读写保护管脚(/WP)被设置位IO2和IO3功能使用
---------------------
版权声明:本文为CSDN博主「尘埃世界」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lalala098/article/details/81302579/ */

/*----------区/块写保护----------
#define BPB_NONE   0x0000           //无保护
#define BPB_T128KB (0x01 << 2)      //保护块 126 & 127   地址: 7E0000h – 7FFFFFh
#define BPB_T256KB (0x02 << 2)      //保护块 124 ~ 127   地址: 7C0000h – 7FFFFFh
#define BPB_T512KB (0x03 << 2)      //保护块 120 ~ 127   地址: 780000h – 7FFFFFh
#define BPB_T1MB   (0x04 << 2)      //保护块 112 ~ 127   地址: 700000h – 7FFFFFh
#define BPB_T2MB   (0x05 << 2)      //保护块  96 ~ 127   地址: 600000h – 7FFFFFh
#define BPB_T4MB   (0x06 << 2)      //保护块  64 ~ 127   地址: 400000h – 7FFFFFh
#define BPB_B128KB (0x09 << 2)      //保护块   0 & 1     地址: 000000h – 01FFFFh
#define BPB_B256KB (0x0A << 2)      //保护块   0 ~ 3     地址: 000000h – 03FFFFh
#define BPB_B512KB (0x0B << 2)      //保护块   0 ~ 7     地址: 000000h – 07FFFFh
#define BPB_B1MB   (0x0C << 2)      //保护块   0 ~ 15    地址: 000000h – 0FFFFFh
#define BPB_B2MB   (0x0D << 2)      //保护块   0 ~ 31    地址: 000000h – 1FFFFFh
#define BPB_B4MB   (0x0E << 2)      //保护块   0 ~ 63    地址: 000000h – 3FFFFFh
#define BPB_ALL    (0x07 << 2)      //全部保护 0 ~ 127   地址: 000000h – 7FFFFFh
#define BPS_T4KB   (0x11 << 2)      //保护块 127         地址: 7FF000h – 7FFFFFh
#define BPS_T8KB   (0x12 << 2)      //保护块 127         地址: 7FE000h – 7FFFFFh
#define BPS_T16KB  (0x13 << 2)      //保护块 127         地址: 7FC000h – 7FFFFFh
#define BPS_T32KB  (0x14 << 2)      //保护块 127         地址: 7F8000h – 7FFFFFh
#define BPS_B4KB   (0x19 << 2)      //保护块 0           地址: 000000h – 000FFFh
#define BPS_B8KB   (0x1A << 2)      //保护块 0           地址: 000000h – 001FFFh
#define BPS_B16KB  (0x1B << 2)      //保护块 0           地址: 000000h – 003FFFh
#define BPS_B32KB  (0x1C << 2)      //保护块 0           地址: 000000h – 007FFFh
----------*/

/*----------状态寄存器保护----------*/
#define SRP_SP   0x0000      //软件保护  WP管脚没有控制权,由“WEL”位控制
#define SRP_HP   0x0080      //硬件保护  由管脚”WP“ 控制 WP 为低电平时无法写入,高电平时:在WEL=1之后可以写入
#define SRP_PSL  0x0100      //电源控制  在断电之后的下一次通电后可以写入
#define SRP_OTP  0x0180      //一次性    芯片只可以写入一次

#define ZERO_DATA 0x00                         //读取数据时发送0x00

void W25Q64FlashInit();                        //初始化函数
void Write_EN();                               //W25Q644写使能函数
void Write_NOEN();                             //W25Q644写禁能函数
void FLASH_Busy();                             //判断Flash是否繁忙
uint16_t FLASH_ReadSR();                       //读取状态寄存器
void FLASH_WriteSR(uint16_t sr);               //写状态寄存器
/*-------------读取数据-------------*/
void FLASH_Read_Data(uint8_t *dat, uint32_t add, uint32_t len);
/*----------写入指定个数的数据----------*/
void FLASH_Write_Data(uint8_t *dat, uint32_t add, uint16_t len);
/*----------连续数的数据写入----------*/
void FLASH_SWrite_Data(uint8_t *dat, uint32_t add, uint32_t len);
void FLASH_Format();                            //格式化flash
void FLASH_Sector_Erase(uint32_t sector);       //扇区擦除
void FLASH_Block_Erase(uint32_t block);         //块擦除
/*-------------串口数据流模式写入数据-------------*/
void FLASH_ContReadData(uint32_t add);
void FLASH_ReadID(uint8_t *id);                 //读取器件ID



#endif
串口配置


usart.c


#include "usart.h"
__IO uint8_t CocheData[64];            //临时数据缓存
__IO uint8_t count=0;                  //接收计数
__IO uint8_t TimeLag = 0;              //数据帧判断时间
__IO uint8_t FLAG_USART1_IT=0;         //串口1中断标志 表示接收到数据 0表示未中断,1表示发生中断
__IO uint8_t FLAG_FrameData = 0;       //用来表示一帧数据接收完成
__IO uint8_t FLAG_FlowData = 0;        //数据流模式标识
__IO uint8_t FLAG_StartFlowData;       //数据流模式开始/结束传输标识
__IO uint8_t FLAG_FLASH_WRITE=0;       //flash写入标识 1表示可以写入一个字节,由flash写入函数清零串口中断置位


/*串口1初始化函数*/
void init_usart1() {                                               
        GPIO_InitTypeDef GPIOA_InitStructure;                          //定义GPIOA初始化结构体变量
        USART_InitTypeDef USART_InitStructure;                         //定义串口初始化结构体变量
        NVIC_InitTypeDef NVIC_InitStructure;                           //定义中断初始化结构体变量

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//ENABLE THE GPIOA    使能GPIOA时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//ENABLE USART1      使能串口1时钟

        GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_9;               //启用GPIOA Pin9引脚 串口发送引脚
        GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //工作模式  复用推挽输出
        GPIOA_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //工作频率50MHz
        GPIO_Init(GPIOA, &GPIOA_InitStructure);                  //初始化GPIOA

        GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_10;             //启用GPIOA Pin10引脚 串口接收引脚
        GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //工作模式  悬空输入
        GPIO_Init(GPIOA, &GPIOA_InitStructure);                 //初始化GPIO

        USART_InitStructure.USART_BaudRate = 115200;                          //设置串口1的波特率
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;           //设置数据长度
        USART_InitStructure.USART_StopBits = USART_StopBits_1;                //设置停止位为1位
        USART_InitStructure.USART_Parity = USART_Parity_No;                   //设置奇偶校验为无校验
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;       //启用接收和传输模式
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //设置硬件流模式
        USART_Init(USART1, &USART_InitStructure);                     //初始化串口1
        USART_Cmd(USART1, ENABLE);                                   //使能串口1
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);               //使能串口1中断
        USART_ClearFlag(USART1, USART_IT_RXNE);                      //清除接收缓存非空

        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;               //指定串口1的中断
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                 //使能串口1中断
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;       //抢占优先级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;              //中断优先级
        NVIC_Init(&NVIC_InitStructure);
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);                 //中断优先级分组
}

/*发送单个字节*/
void USART1_SendChar(uint8_t dat){
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){  //判断发送缓存区是否为空 TXE是发送缓存区清空标志
                }
              USART_SendData(USART1,dat);
}

/*发送多个字节*/
void USART1_SendMulti(uint8_t *dat,uint8_t len){
  uint8_t i;
        for(i=0;i             USART1_SendChar(*dat++);
        }
}

/*发送字符串*/
void USART1_SendString(uint8_t *dat) {
        while (*dat != '') {       //遇到结束符停止发送
                USART1_SendChar(*dat++);
        }
}

/*串口1中断函数*/
void USART1_IRQHandler(void) {                                                                  
        /*串口1指令模式*/
        if (USART_GetFlagStatus(USART1, USART_IT_RXNE)!=RESET) {    //判断接收缓冲区是否非空
                CocheData[count] = USART_ReceiveData(USART1);           //把接收到的数据暂时存储到数据缓存数据
                FLAG_USART1_IT = 1;                                     //置位中断标志

                /*串口1数据流模式*/
                if (FLAG_FlowData) {
                        FLAG_StartFlowData = 1;                             //置位数据流模式开始传输标识,表示开始传输或是在传输中
                        }
                }
                count++;                                                //统计接收的个数
        }


/*判断是否接收完一帧数据 和判断数据流模式是否结束*/
void Judge_ETB() {                 
        if (FLAG_USART1_IT) {                //判断串口是否发生中断
                FLAG_USART1_IT = 0;              //发生中断,清除中断标志
                TimeLag = 200;                    //重载判断数据帧结束时间
        }
        else if (TimeLag>0) {                //没有发生中断开始计时30ms
                TimeLag--;                 
                if (TimeLag==0){                 //30ms后串口没有发生中断,
                          
                        /*串口1数据流模式*/
                        if (FLAG_FlowData) {
                                FLAG_StartFlowData = 0;        //清除数据流模式开始/结束传输标识,告诉后面的程序要数据流模式已经结束了
                                return;                        //数据流模式不需要置位数据帧标志
                        }

                        FLAG_FrameData = 1;                //置位数据帧标志,告诉后面的程序要开始处理了
                }
        }
       
}
usart.h


#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"

void USART1_SendChar(uint8_t dat);               //发送单个字符
void USART1_SendMulti(uint8_t *dat,uint8_t len);   //发送多个字节
void USART1_SendString(uint8_t *dat);             //发送字符串
void init_usart1();     //串口1初始化函数
void Judge_ETB();       //判断是否接收完一帧数据
#endif
命令模式


command.c


#include "command.h"

extern __IO uint8_t CocheData[64];      //串口临时数据缓存
extern __IO uint8_t count;              //接收计数
extern __IO uint8_t FLAG_FrameData;     //用来表示一帧数据接收完成 或是数据流接收完成
extern __IO uint8_t FLAG_FlowData;      //数据流模式标识


/*把数据从数据缓冲区拷贝到存储区*/
char CopyCocheData(uint8_t *dat, uint16_t len) {         
        uint8_t i;                     
       
        if(len>=count){                   //比较接收长度;len 程序处理的字符串长度 count 串口实际接收的字符串长度
                i = count;
          count = 0;
        for (char j = 0; j < i; j++)
                dat[j] = CocheData[j];        //把数据从缓冲区拷贝出来以免被下一帧数据破坏,在闲置时期处理,释放缓冲区
        return i;                         //返回接收到的数据长度
        }
        else{
          i=len;
                count = 0;
                for(char j=0;j                   dat[j] = CocheData[j];
        return i;
        }
        }
       

/*指令比较函数,比较接收的指令是否与指令列表的指令一致*/
char CompareCommand(uint8_t *A, uint8_t *B, uint8_t len) {
        while (len--) {                 //比较
                if (*A++ != *B++) {
                        return 0;
                }
        }
        return 1;
}



/*监视是否有指令到来*/
void MoniorCM() {
        if (FLAG_FrameData) {
                FLAG_FrameData = 0;
                Command();
        }
}


/*指令处理函数 处理对应指令的动作*/
void Command() {
        uchar i;
        uint8_t len;
        uint8_t cmd[64];
       

        /*------------命令列表------------*/
        uchar  cmd1[] = "LED1 on";
        uchar  cmd2[] = "LED1 off";
        uchar  cmd3[] = "LED2 on";
        uchar  cmd4[] = "LED2 off";
        uchar  cmd5[] = "return";
        uchar  cmd6[] = "flash -";
        uchar  cmd7[] = "print ";   
        uchar  cmd8[] = "font size_";
       
        uchar warn[] = "There is no corresponding commandrn";  //没有对应的命令警告语
        uchar hint[] = " ->rn";                                //命令执行成功标识符
       
        /*------------命令长度汇总------------*/
        uchar  cmdLen[] = { sizeof(cmd1) - 1, sizeof(cmd2) - 1, sizeof(cmd3) - 1, sizeof(cmd4) - 1, sizeof(cmd5) - 1,
        sizeof(cmd6) - 1 ,sizeof(cmd7)-1 ,sizeof(cmd8)-1};
        /*------------命令地址汇总------------*/
        uchar  *cmdPtr[] = { cmd1,cmd2, cmd3, cmd4, cmd5, cmd6 ,cmd7 ,cmd8};

    len = CopyCocheData(cmd, sizeof(cmd));

        /*------------遍历命令列表------------*/
        for (i = 0; i < sizeof(cmdLen); i++) {
                if (len >= cmdLen) {      //首先比较接收到的数据帧长度是否大于命令长度
                        if (CompareCommand(cmd, cmdPtr, cmdLen)) {
                                break;
                        }
                }
        }
       
        /*------------命令对应要执行的动作-----------*/
        switch (i) {
        case 0:
                LED1_ON;      //打开LED1
                        break;
        case 1:
                LED1_OFF;    //关闭LLED1
                break;
        case 2:
                LED2_ON;     //打开LED2
                break;
        case 3:
                LED2_OFF;    //关闭LED2
                break;
        case 4:
                cmd[len] = '';              //在数据帧后面添加换行符
                USART1_SendMulti(&cmd[cmdLen[4]+1],len-cmdLen[4]);  //返回命令后面的字符串
                break;
        case 5:                         //flash操作
                CMD_Flash(&cmd[cmdLen[5]]); //把命令后面的数据交给flash命令函数处理
                break;
        case 6:
                cmd[len] = '';              //在数据帧后面添加结束符
                LCD_putCH(&cmd[cmdLen[6]]);   //在LCD上显示空格后面的字符串
                break;
        case 7:
         FontSize(&cmd[cmdLen[7]]);
                break;
        default:
                USART1_SendString(warn);      //发送没有对应的命令警告语
                return;                       //没有对应的命令先发送警告语,然后结束此函数
        }
        USART1_SendString(cmdPtr);         //返回执行的命令;然后再发送命令执行成功标识符
        USART1_SendString(hint);   
}

/*----------------------------分割线----------------------------*/
/*-----------------------以下为命令函数区-----------------------*/


/*------------问答模式返回32位无符号整型------------*/
uint32_t CMD_QAM(uint8_t *p){
  uint32_t addr=0;
        uint8_t data[4];
       
        USART1_SendString(p);
       
        while(FLAG_FrameData==0);
       
        FLAG_FrameData = 0;
        CopyCocheData(data,sizeof(data));
       
        addr=data[0];
        addr=(addr<<8)|data[1];
        addr=(addr<<8)|data[2];
        addr=(addr<<8)|data[3];
        return addr;
}
/*------------问答模式返回字符串------------*/
char CMD_SQAM(uint8_t *data ,uint8_t *p){
        uint8_t len=0;
        USART1_SendString(p);
        while(FLAG_FrameData==0);
        FLAG_FrameData = 0;
        len = CopyCocheData(data,64);
return len;
}


/*flash命令函数*/
void CMD_Flash(uint8_t *p) {
        uint8_t data[64]={0};           //临时存储要处理的字符串数据
        uint32_t addr=0;                //flash要操作的地址 在有需要的时候赋值
        uint32_t Idata=0;               //临时存储需要处理的整型数据
       
       
        if (*p == 'w') {                //判断命令的参数是否为w 写数据
                if(*++p == 's'){
                 addr=CMD_QAM("请输入24为地址:rn");
            Idata=CMD_QAM("请输入要读取的个数:rn");
                        char len = CMD_SQAM(data,"请输入字符串(最多64个):");

                //写入数据
                FLASH_SWrite_Data(data,addr,len);

                //发送数据
               
                USART1_SendString("rn");
                       
                }
                else{
                addr=CMD_QAM("请输入24为地址:rn");
               
                FLAG_FlowData = 1;          //启动串口1的数据流模式
                USART1_SendString("flash操作配置完成rn");
               
               
                FLASH_ContReadData(addr);
          FLAG_FlowData=0;
                }
        }
       
        else if (*p == 'r') {           //判断命令的参数是否为r 读数据
        addr=CMD_QAM("请输入24为地址:rn");
            Idata=CMD_QAM("请输入要读取的个数:rn");

                //读出数据
                FLASH_Read_Data(data,addr,Idata);  

                //发送数据
                USART1_SendMulti(data,Idata);
                USART1_SendString("rn");

        }
       
        else if (*p == 'i'){             //判断命令的参数是否为i  读取器件ID
          FLASH_ReadID(data);
                USART1_SendMulti(data,2);
                USART1_SendString("rn");
        }
       
        else if (*p == 'f'){             //判断命令的参数是否为f  格式化flash
          FLASH_Format();
        }
       
        else if (*p == 's'){             //判断命令的参数是否为s  扇区擦除
          addr=CMD_QAM("请输入24为地址:rn");
                FLASH_Sector_Erase(addr);
        }
       
        else if (*p == 'b'){             //判断命令的参数是否为b  块擦除
          addr=CMD_QAM("请输入24为地址:rn");
                FLASH_Block_Erase(addr);
        }
       
        else if (*p == 't'){
          
        }
       
        else {                            //没有找到参数
                USART1_SendString("flash命令格式有问题,示范: flash -w 0x00 rn  参数: -w 向flash写数据rn -r 读取flash的数据rn");
        }
       
}

void FontSize(uint8_t *dat){
     if(*dat=='1'){
                   if(*++dat=='6'){
                 ConfigPrintSize(16,16);
                                 return;
                 }
                 }
         else if(*dat=='3'){
         if(*dat=='2'){
         ConfigPrintSize(32,32);
         }
         }
}
command.h


#ifndef __COMMAND_H
#define __COMMAND_H

#include "stm32f10x.h"

#define uchar unsigned char

char CopyCocheData(uint8_t *dat, uint16_t len);            //把数据从数据缓冲区拷贝到指令执行区
char CompareCommand(uint8_t *A, uint8_t *B, uint8_t len);  //指令比较函数,比较接收的指令是否与指令列表的指令一致
void MoniorCM();                                           //监视是否有指令到来
void Command();                                            //指令处理函数 处理对应指令的动作
uint32_t CMD_FlashParrm(uint8_t *p);                       //问答模式返回32位无符号整型
char CMD_SQAM(uint8_t *data ,uint8_t *p);                  //问答模式字符串 函数返回的是字符串长度
void CMD_Flash(uint8_t *p);                                //flash命令函数
void FontSize(uint8_t *dat);                               //LCD字体大小设置

#endif


举报

更多回帖

发帖
×
20
完善资料,
赚取积分