当前位置:网站首页 > 编程语言 > 正文

jflash读取单片机程序(单片机读取flash地址)



代码分析

SPI硬件相关宏定义

我们把SPI硬件相关的配置都以宏的形式定义到 “bsp_spi_ flash.h”文件中,见 。

以上代码根据硬件连接,把与FLASH通讯使用的SPI号 、GPIO等都以宏封装起来, 并且定义了控制CS(NSS)引脚输出电平的宏,以便配置产生起始和停止信号时使用。

初始化SPI的 GPIO

利用上面的宏,编写SPI的初始化函数,见 。

与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,配置好复用功能。GPIO初始化流程如下:

(1) 使用GPIO_InitTypeDef定义GPIO初始化结构体变量, 以便下面用于存储GPIO配置;

(2) 调用库函数RCC_APB2PeriphClockCmd 来使能SPI引脚使用的GPIO端口时钟。

(3) 向GPIO初始化结构体赋值,把SCK/MOSI/MISO引脚初始化成复用推挽模式。 而CS(NSS)引脚由于使用软件控制,我们把它配置为普通的推挽输出模式。

(4) 使用以上初始化结构体的配置,调用GPIO_Init函数向寄存器写入参数, 完成GPIO的初始化。

配置SPI的模式

以上只是配置了SPI使用的引脚,对SPI外设模式的配置。在配置STM32的SPI模式前,我们要先了解从机端的SPI模式。 本例子中可通过查阅FLASH数据手册《W25Q64》获取。根据FLASH芯片的说明,它支持SPI模式0及模式3,支持双线全双工, 使用MSB先行模式,支持最高通讯时钟为104MHz,数据帧长度为8位。我们要把STM32的SPI外设中的这些参数配置一致。 见 。

这段代码中,把STM32的SPI外设配置为主机端,双线全双工模式,数据帧长度为8位,使用SPI模式3(CPOL=1,CPHA=1), NSS引脚由软件控制以及MSB先行模式。代码中把SPI的时钟频率配置成了4分频,实际上可以配置成2分频以提高通讯速率, 读者可亲自尝试一下。最后一个成员为CRC计算式,由于我们与FLASH芯片通讯不需要CRC校验,并没有使能SPI的CRC功能, 这时CRC计算式的成员值是无效的。

赋值结束后调用库函数SPI_Init把这些配置写入寄存器,并调用SPI_Cmd函数使能外设。

使用SPI发送和接收一个字节的数据

初始化好SPI外设后,就可以使用SPI通讯了,复杂的数据通讯都是由单个字节数据收发组成的, 我们看看它的代码实现,见 。

SPI_FLASH_SendByte发送单字节函数中包含了等待事件的超时处理,这部分原理跟I2C中的一样,在此不再赘述。

SPI_FLASH_SendByte函数实现了前面讲解的“SPI通讯过程”:

(1) 本函数中不包含SPI起始和停止信号,只是收发的主要过程, 所以在调用本函数前后要做好起始和停止信号的操作

(2) 对SPITimeout变量赋值为宏SPIT_FLAG_TIMEOUT。这个SPITimeout变量在下面的while循环中每次循环减1, 该循环通过调用库函数SPI_I2S_GetFlagStatus检测事件,若检测到事件,则进入通讯的下一阶段, 若未检测到事件则停留在此处一直检测,当检测SPIT_FLAG_TIMEOUT次都还没等待到事件则认为通讯失败, 调用的SPI_TIMEOUT_UserCallback输出调试信息,并退出通讯;

(3) 通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空, 则表示可能存在的上一个数据已经发送完毕;

(4) 等待至发送缓冲区为空后,调用库函数SPI_I2S_SendData把要发送的数据“byte”写入到SPI的数据寄存器DR, 写入SPI数据寄存器的数据会存储到发送缓冲区,由SPI外设发送出去;

(5) 写入完毕后等待RXNE事件,即接收缓冲区非空事件。 由于SPI双线全双工模式下MOSI与MISO数据传输是同步的(请对比“SPI通讯过程”阅读),当接收缓冲区非空时, 表示上面的数据发送完毕,且接收缓冲区也收到新的数据;

(6) 等待至接收缓冲区非空时,通过调用库函数SPI_I2S_ReceiveData读取SPI的数据寄存器DR,就可以获取接收缓冲区中的新数据了。 代码中使用关键字“return”把接收到的这个数据作为SPI_FLASH_SendByte函数的返回值, 所以我们可以看到在下面定义的SPI接收数据函数SPI_FLASH_ReadByte,它只是简单地调用了SPI_FLASH_SendByte函数发送数据“Dummy_Byte”, 然后获取其返回值(因为不关注发送的数据,所以此时的输入参数“Dummy_Byte”可以为任意值)。 可以这样做的原因是SPI的接收过程和发送过程实质是一样的,收发同步进行,关键在于我们的上层应用中,关注的是发送还是接收的数据。

控制FLASH的指令

搞定SPI的基本收发单元后,还需要了解如何对FLASH芯片进行读写。FLASH芯片自定义了很多指令, 我们通过控制STM32利用SPI总线向FLASH芯片发送指令,FLASH芯片收到后就会执行相应的操作。

而这些指令,对主机端(STM32)来说,只是它遵守最基本的SPI通讯协议发送出的数据,但在设备端(FLASH芯片)把这些数据解释成不同的意义, 所以才成为指令。查看FLASH芯片的数据手册《W25Q64》,可了解各种它定义的各种指令的功能及指令格式, 见表 。

FLASH常用芯片指令表

该表中的第一列为指令名,第二列为指令编码,第三至第N列的具体内容根据指令的不同而有不同的含义。 其中带括号的字节参数,方向为FLASH向主机传输,即命令响应,不带括号的则为主机向FLASH传输。 表中“A0~A23”指FLASH芯片内部存储器组织的地址;“M0~M7”为厂商号(MANUFACTURERID); “ID0-ID15”为FLASH芯片的ID;“dummy”指该处可为任意数据;“D0~D7”为FLASH内部存储矩阵的内容。

在FLSAH芯片内部,存储有固定的厂商编号(M7-M0)和不同类型FLASH芯片独有的编号(ID15-ID0), 见表 。

FLASH数据手册的设备ID说明

通过指令表中的读ID指令“JEDEC ID”可以获取这两个编号,该指令编码为“9F h”, 其中“9F h”是指16进制数“9F” (相当于C语言中的0x9F)。 紧跟指令编码的三个字节分别为FLASH芯片输出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)”。

此处我们以该指令为例,配合其指令时序图进行讲解,见图 。

FLASH读ID指令JEDEC_ID的时序

主机首先通过MOSI线向FLASH芯片发送第一个字节数据为“9F h”,当FLASH芯片收到该数据后,它会解读成主机向它发送了“JEDEC指令”, 然后它就作出该命令的响应:通过MISO线把它的厂商ID(M7-M0)及芯片类型(ID15-0)发送给主机,主机接收到指令响应后可进行校验。 常见的应用是主机端通过读取设备ID来测试硬件是否连接正常,或用于识别设备。

对于FLASH芯片的其它指令,都是类似的,只是有的指令包含多个字节,或者响应包含更多的数据。

实际上,编写设备驱动都是有一定的规律可循的。首先我们要确定设备使用的是什么通讯协议。如上一章的EEPROM使用的是I2C, 本章的FLASH使用的是SPI。那么我们就先根据它的通讯协议,选择好STM32的硬件模块,并进行相应的I2C或SPI模块初始化。 接着,我们要了解目标设备的相关指令,因为不同的设备,都会有相应的不同的指令。如EEPROM中会把第一个数据解释为内部存储矩阵的地址(实质就是指令)。 而FLASH则定义了更多的指令,有写指令,读指令,读ID指令等等。最后,我们根据这些指令的格式要求,使用通讯协议向设备发送指令,达到控制设备的目标。

定义FLASH指令编码表

为了方便使用,我们把FLASH芯片的常用指令编码使用宏来封装起来,后面需要发送指令编码的时候我们直接使用这些宏即可, 见 。

读取FLASH芯片ID

根据“JEDEC”指令的时序,我们把读取FLASH ID的过程编写成一个函数,见 。

这段代码利用控制CS引脚电平的宏“SPI_FLASH_CS_LOW/HIGH”以及前面编写的单字节收发函数SPI_FLASH_SendByte, 很清晰地实现了“JEDEC ID”指令的时序:发送一个字节的指令编码“W25X_JedecDeviceID”,然后读取3个字节, 获取FLASH芯片对该指令的响应,最后把读取到的这3个数据合并到一个变量Temp中,然后作为函数返回值, 把该返回值与我们定义的宏“sFLASH_ID”对比,即可知道FLASH芯片是否正常。

FLASH写使能以及读取当前状态

在向FLASH芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能, 见 。

与EEPROM一样,由于FLASH芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的, 所以在写操作后需要确认FLASH芯片“空闲”时才能进行再次写入。为了表示自己的工作状态,FLASH芯片定义了一个状态寄存器, 见图 。

FLASH芯片的状态寄存器

我们只关注这个状态寄存器的第0位“BUSY”,当这个位为“1”时,表明FLASH芯片处于忙碌状态, 它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作。

利用指令表中的“Read Status Register”指令可以获取FLASH芯片状态寄存器的内容, 其时序见图 。

读取状态寄存器的时序

只要向FLASH芯片发送了读状态寄存器的指令,FLASH芯片就会持续向主机返回最新的状态寄存器内容, 直到收到SPI通讯的停止信号。据此我们编写了具有等待FLASH芯片写入结束功能的函数,见 。

这段代码发送读状态寄存器的指令编码“W25X_ReadStatusReg”后,在while循环里持续获取寄存器的内容并检验它的“WIP_Flag标志”(即BUSY位), 一直等待到该标志表示写入结束时才退出本函数,以便继续后面与FLASH芯片的数据通讯。

FLASH扇区擦除

由于FLASH存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。 所以这里涉及到数据“擦除”的概念,在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”, 在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵,在要存储数据“0”时,才更改该位。

通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例子中的FLASH芯片支持“扇区擦除”、“块擦除”以及“整片擦除”, 见表 。

本实验FLASH芯片的擦除单位

FLASH芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含16个扇区, 其内部存储矩阵分布见图 。。

FLASH芯片的存储矩阵

使用扇区擦除指令“”可控制FLASH芯片开始擦写, 其指令时序见图 。

扇区擦除时序

扇区擦除指令的第一个字节为指令编码,紧接着发送的3个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前, 还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕, 代码实现见 。

这段代码调用的函数在前面都已讲解,只要注意发送擦除地址时高位在前即可。调用扇区擦除指令时注意输入的地址要对齐到4KB。

FLASH的页写入

目标扇区被擦除完毕后,就可以向它写入数据了。与EEPROM类似,FLASH芯片也有页写入命令, 使用页写入命令最多可以一次向FLASH传输256个字节的数据,我们把这个单位为页大小。 FLASH页写入的时序见图 。

FLASH芯片页写入

从时序图可知,第1个字节为“页写入指令”编码,2-4字节为要写入的“地址A”,接着的是要写入的内容,最多个可以发送256字节数据, 这些数据将会从“地址A”开始,按顺序写入到FLASH的存储矩阵。若发送的数据超出256个,则会覆盖前面发送的数据。

与擦除指令不一样,页写入指令的地址并不要求按256字节对齐,只要确认目标存储单元是擦除状态即可(即被擦除后没有被写入过)。 所以,若对“地址x”执行页写入指令后,发送了200个字节数据后终止通讯,下一次再执行页写入指令,从“地址(x+200)”开始写入200个字节也是没有问题的(小于256均可)。 只是在实际应用中由于基本擦除单元是4KB,一般都以扇区为单位进行读写,想深入了解,可学习我们的“FLASH文件系统”相关的例子。

把页写入时序封装成函数,其实现见 。

这段代码的内容为:先发送“写使能”命令,接着才开始页写入时序,然后发送指令编码、地址,再把要写入的数据一个接一个地发送出去, 发送完后结束通讯,检查FLASH状态寄存器,等待FLASH内部写入结束。

不定量数据写入

应用的时候我们常常要写入不定量的数据,直接调用“页写入”函数并不是特别方便,所以我们在它的基础上编写了“不定量数据写入”的函数, 基实现见 。

这段代码与EEPROM章节中的“快速写入多字节”函数原理是一样的,运算过程在此不再赘述。区别是页的大小以及实际数据写入的时候, 使用的是针对FLASH芯片的页写入函数,且在实际调用这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态。

从FLASH读取数据

相对于写入,FLASH芯片的数据读取要简单得多,使用读取指令“Read Data”即可, 其指令时序见图 。

SPI_FLASH读取数据时序

发送了指令编码及要读的起始地址后,FLASH芯片就会按地址递增的方式返回存储矩阵的内容,读取的数据量没有限制, 只要没有停止通讯,FLASH芯片就会一直返回数据。代码实现见 。

由于读取的数据量没有限制,所以发送读命令后一直接收NumByteToRead个数据到结束即可。

到此这篇jflash读取单片机程序(单片机读取flash地址)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • webflux(webflux使用)2025-04-26 15:36:08
  • 华为模拟器怎么使用(华为模拟器怎么使用加速器)2025-04-26 15:36:08
  • 单片机程序的入口地址是?(单片机程序的入口地址是0001h)2025-04-26 15:36:08
  • hiplot官网(hipee官网)2025-04-26 15:36:08
  • 苹果无线耳机老是断开连接(苹果无线耳机老是断开连接怎么办)2025-04-26 15:36:08
  • linux修改文件权限777的命令(linux修改文件权限的命令是)2025-04-26 15:36:08
  • 初音未来绿色代码(初音未来指令码)2025-04-26 15:36:08
  • 查nat类型(查nat类型工具)2025-04-26 15:36:08
  • u盘启动盘的制作(u盘启动盘的制作心得体会)2025-04-26 15:36:08
  • wifi字典爆破(wifi字典爆破app)2025-04-26 15:36:08
  • 全屏图片