大纲

点这里下载:W25Q64手册 提取码:t提取码  lds

点击下载      字库下载源码和串口助手     提取吗  7tg0   (包含字体文件)

点击下载      完整工程文件   提取码:8uzs

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总线最大时钟频率为26MHzSPI_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

操作流程

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);     //读取状态寄存器2dat = SPI2_Sed_RecData(ZERO_DATA);CS_OFF;                             //关片选dat = dat << 8;                     //把数据往后位移8位,因为状态寄存器2在高8位CS_ON;SPI2_Sed_RecData(READ_STATUS);      //读取状态寄存器1dat |= 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;                          //用于第二次判断剩余写入字节数是否大于256TD_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                         //读取数据时发送0x00void 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;      //工作频率50MHzGPIO_Init(GPIOA, &GPIOA_InitStructure);                  //初始化GPIOAGPIOA_InitStructure.GPIO_Pin = GPIO_Pin_10;             //启用GPIOA Pin10引脚 串口接收引脚GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //工作模式  悬空输入GPIO_Init(GPIOA, &GPIOA_InitStructure);                 //初始化GPIOUSART_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);                     //初始化串口1USART_Cmd(USART1, ENABLE);                                   //使能串口1USART_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<len;i++){           //发送个数USART1_SendChar(*dat++);}
}/*发送字符串*/
void USART1_SendString(uint8_t *dat) {while (*dat != '\0') {       //遇到结束符停止发送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) {                //没有发生中断开始计时30msTimeLag--;                 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<len;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 command\r\n";  //没有对应的命令警告语uchar hint[] = " ->\r\n";                                //命令执行成功标识符/*------------命令长度汇总------------*/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[i]) {      //首先比较接收到的数据帧长度是否大于命令长度if (CompareCommand(cmd, cmdPtr[i], cmdLen[i])) {break;}}}/*------------命令对应要执行的动作-----------*/switch (i) {case 0:LED1_ON;      //打开LED1break;case 1:LED1_OFF;    //关闭LLED1break;case 2:LED2_ON;     //打开LED2break;case 3:LED2_OFF;    //关闭LED2break;case 4:cmd[len] = '\0';              //在数据帧后面添加换行符USART1_SendMulti(&cmd[cmdLen[4]+1],len-cmdLen[4]);  //返回命令后面的字符串break;case 5:                         //flash操作CMD_Flash(&cmd[cmdLen[5]]); //把命令后面的数据交给flash命令函数处理break;case 6:cmd[len] = '\0';              //在数据帧后面添加结束符LCD_putCH(&cmd[cmdLen[6]]);   //在LCD上显示空格后面的字符串break;case 7:FontSize(&cmd[cmdLen[7]]);break;default:USART1_SendString(warn);      //发送没有对应的命令警告语return;                       //没有对应的命令先发送警告语,然后结束此函数}USART1_SendString(cmdPtr[i]);         //返回执行的命令;然后再发送命令执行成功标识符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为地址:\r\n");Idata=CMD_QAM("请输入要读取的个数:\r\n");char len = CMD_SQAM(data,"请输入字符串(最多64个):");//写入数据FLASH_SWrite_Data(data,addr,len);//发送数据USART1_SendString("\r\n");}else{addr=CMD_QAM("请输入24为地址:\r\n");FLAG_FlowData = 1;          //启动串口1的数据流模式USART1_SendString("flash操作配置完成\r\n");FLASH_ContReadData(addr);FLAG_FlowData=0;}}else if (*p == 'r') {           //判断命令的参数是否为r 读数据addr=CMD_QAM("请输入24为地址:\r\n");Idata=CMD_QAM("请输入要读取的个数:\r\n");//读出数据FLASH_Read_Data(data,addr,Idata);  //发送数据USART1_SendMulti(data,Idata);USART1_SendString("\r\n");}else if (*p == 'i'){             //判断命令的参数是否为i  读取器件IDFLASH_ReadID(data);USART1_SendMulti(data,2);USART1_SendString("\r\n");}else if (*p == 'f'){             //判断命令的参数是否为f  格式化flashFLASH_Format();}else if (*p == 's'){             //判断命令的参数是否为s  扇区擦除addr=CMD_QAM("请输入24为地址:\r\n");FLASH_Sector_Erase(addr);}else if (*p == 'b'){             //判断命令的参数是否为b  块擦除addr=CMD_QAM("请输入24为地址:\r\n");FLASH_Block_Erase(addr);}else if (*p == 't'){}else {                            //没有找到参数USART1_SendString("flash命令格式有问题,示范: flash -w 0x00 \r\n  参数: -w 向flash写数据\r\n -r 读取flash的数据\r\n");}}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 charchar 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

stm32f103学习笔记 通过串口将字库文件下载到flash中相关推荐

  1. stm32学习笔记----双串口同时打开时的printf()问题

    stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...

  2. Qt5学习笔记之串口助手四:增加16进制/ASCII切换、周期发送

    目录 1. 概述 2. 16进制/ASCII发送 2.1 功能实现 2.2 界面修改 3. 接收框显示发送内容 3.1 以16进制/ASCII显示 3.1.1 界面修改 3.1.2 功能实现 3.2 ...

  3. STM32F103学习笔记(5)——数码管驱动TM1650使用

    一.简介 TM1650 是一种带键盘扫描接口的 LED(发光二极管显示器)驱动控制专用电路.内部集成有 MCU 输入输出控制数字接口.数据锁存器.LED 驱动.键盘扫描.辉度调节等电路.TM1650 ...

  4. STM32F103学习笔记四 时钟系统

    STM32F103学习笔记四 时钟系统 本文简述了自己学习时钟系统的一些框架,参照风水月 1. 单片机中时钟系统的理解 1.1 概述 时钟是单片机的脉搏,是单片机的驱动源 用任何一个外设都必须打开相应 ...

  5. KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记3——串口Stdio实现

    KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记3--串口Stdio实现 一.介绍 任务目标 二.工程创建 三.软件设计 第一步,BSP构建 1, 添加前面的pll_config文件 2 ...

  6. 【K210】K210学习笔记五——串口通信

    [K210]K210学习笔记五--串口通信 前言 K210如何进行串口通信 K210串口配置 K210串口发送相关定义 K210串口接收相关定义 K210串口发送接收测试 完整源码 前言 本人大四学生 ...

  7. STM32F103学习笔记(2)——收音机模块TEA5767使用

    一.简介 TEA5767是由菲利普公司推出的一款低功耗立体声收音机接收器芯片.频率范围从76-108MHz自动数字调谐.高灵敏度,高稳定性,低噪音,收音模块.一片低功耗电调谐调频立体声收音机电路,其内 ...

  8. oracle修改asm参数文件,学习笔记:Oracle RAC参数文件管理 修改创建asm中的spfile文件...

    天萃荷净 Oracle rac创建修改asm中的spfile文件内容 create spfile to asm --查看sid SQL> show parameter instance_name ...

  9. 《游戏设计艺术(第2版)》——学习笔记(20)第20章 世界中的角色

    <游戏设计艺术(第2版)>学习笔记(20) 第20章 世界中的角色 游戏角色的本质 小说角色 电影角色 游戏角色 化身 理想型 白板 创造令人信服的游戏角色 角色窍门1:列出角色的功能 角 ...

最新文章

  1. Python,Opencv cv2.Canny()边缘检测
  2. c++ double 只输出五位_c 语言第四章 在控制台上数据的输入和输出
  3. linux send 失败_linux高性能网络编程之tcp连接的内存使用
  4. (019)java后台开发之第三方库收集
  5. Python 使用正则表达式中的 /b 的时候出现了问题
  6. EXC中时间控件的使用
  7. 新中大oracle实列名,新中大财务软件操作流程(完整版)
  8. mybatis 不生效 参数_Mybatis-日志配置
  9. 一般程序句柄多少linux,一个进程能够打开最大文件句柄数设到多大才合适(Linux)...
  10. js 文本加密 php解密,JavaScript加密和PHP解密
  11. 一个本科毕业生的Java学习之路
  12. 超级好用的5个免费在线版流程图制作网站
  13. Java可以加速播放的播放器,android exoplayer最好用的视频播放器,倍速播放
  14. 老客户营销新招 如何拯救店铺复购率
  15. 【MySQL】34道SQL综合练习详解(员工表、部门表、工资等级表)
  16. x264参数设置详解(x264 settings)
  17. strcpy 和strncpy 的代码和区别
  18. linux安装python任意版本,一键安装和一键卸载shell脚本
  19. Java中有哪几种常见的设计模式
  20. 组织、结构与部分、整体

热门文章

  1. 普通非校园网用户如何使用IPV6连接网络?[已失效]
  2. 让chatGPT使用Tensor flow Keras组装Bert,GPT,Transformer
  3. Fenetre for mac(双窗口管理工具)
  4. xp系统打开计算机硬盘分区,xp硬盘怎么分区?xp系统对硬盘进行分区的方法
  5. Java Web 实战 09 - 多线程基础之定时器
  6. 100条牛的话《转》
  7. dotty编译器语法特性之一幽灵类型
  8. Leetcode 1482题 制作 m 束花所需的最少天数
  9. DNS 域名解析服务配置(建议收藏的教程)
  10. 如何顺利通过毕业答辩