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

您所在的位置:网站首页 怎么下载flsah stm32f103学习笔记 通过串口将字库文件下载到flash中

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

2024-07-13 09:33| 来源: 网络整理| 查看: 265

大纲    

点这里下载: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 芯片引脚图

flash引脚图

1.CS  片选信号,低电平有效,操作flash之前要先拉低CS,这里要注意每执行一条新指令之前必须要使CS管脚有个下降沿信号。    

2.DO 和DI 分别为串行数据的输出输入管脚,    

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

Flash常用指令

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 操作流程

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)&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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3