赞
踩
四、SPI相关寄存器介绍
五、SPI相关HAL库驱动介绍
六、NOR FLASH介绍
6.1、NOR FLASH简介
6.2、NM25Q128简介
6.3、NM25Q128存储结构
6.4、NM25Q128常用指令
6.5、NM25Q128 读/擦除/写 步骤
SPI(Serial Peripheral Interface)是一种常见的集成电路间通信接口标准,它允许一个主设备与一个或多个从设备之间进行全双工同步串行通信。SPI接口以其高速、灵活和简单易用的特性被广泛应用于嵌入式系统和众多电子设备中。
主要应用领域包括但不限于:
SPI接口通常由四条信号线组成,包括:
此外,某些SPI实现可能还包括额外的控制线或中断线,以便增强功能或优化通信。SPI的最大特点是它的高速同步通信能力,使其在需要高速数据传输和精确同步的场合尤为适用。
SPI和IIC(I²C)都是常见的串行通信接口协议,它们都有各自的特点、优缺点以及适合的应用场景:
总结:
SPI更适合高速、实时性强、需要与多个独立从设备进行高速交互的场合,而I²C则适合低成本、低速、轻型设备互联和系统内部通信。两者在实际应用中可根据具体需求选择合适的接口标准。
特性 | SPI | I²C |
---|---|---|
通信方式 | 全双工 | 半双工 |
总线结构 | 每个从设备需要独立的片选线(CS) | 仅需两线(SDA和SCL),允许多设备共享总线 |
时钟控制 | 主设备提供,所有从设备共享同一时钟 | 同SPI,但速率相对较慢 |
通信速率 | 较高,数Mbps到数十Mbps | 较低,典型几kbps到几百kbps,高速模式可达几Mbps |
线路复杂度 | 线路数量随从设备增加而增加 | 仅需两线,简化硬件设计和布线 |
仲裁机制 | 不支持多主控模式,不适合多主设备环境 | 支持多主控模式,内建仲裁机制 |
应用场景 | 大容量存储器读写、显示驱动器控制、高速AD/DA转换器 | 传感器数据读取、IC配置与控制、低速外设控制 |
SPI(Serial Peripheral Interface)结构框图通常包含以下几个关键组成部分:
SPI相关引脚:
数据发送和接收模块:
时钟信号生成:
主控制逻辑:
SPI的结构框图通常展示以上组件之间的连接关系和工作流程,清晰描绘了SPI通信的硬件架构和软件控制逻辑。
SPI数据发送与接收的过程可以这样描述:
数据发送过程:
发送缓冲区:主机(Master)首先将待发送的数据加载到SPI控制器的发送缓冲区中。当缓冲区为空时,即TXE(Transmit Buffer Empty)标志位被置位,表明可以向缓冲区写入新数据。
移位寄存器:一旦数据被写入发送缓冲区,SPI控制器会在下一个时钟脉冲到来时将数据移位至移位寄存器中。随着SCK时钟信号的上升和下降沿,数据按位依次通过MOSI(Master Output, Slave Input)线传输给从机(Slave)。
(地址&数据总线):实际上在SPI协议中,地址和数据通常是结合在一起连续传输的,不存在独立的地址总线。所有信息都通过MOSI线串行发送。
数据接收过程:
MISO发送DATA:从机通过其MISO(Master Input, Slave Output)线将数据串行发送回给主机。在每一个SCK时钟周期内,从机的移位寄存器中的数据位会转移到MISO线上。
移位寄存器:主机端的SPI控制器通过SCK同步接收这些数据,并将其存放在自身的移位寄存器中。
接收缓冲区:当接收到一个完整的数据字后,SPI控制器会将移位寄存器中的数据移入接收缓冲区中。当接收缓冲区中有新的数据可供读取时,RXNE(Receive Buffer Not Empty)标志位会被置位,提示主机可以从SPI接收缓冲区中读取接收到的数据。
总结来说,SPI数据的发送和接收都是通过共享的时钟信号和单向的数据线进行串行通信的,发送和接收操作可以同时进行,因此SPI具有全双工通信能力。在实际应用中,通过对SPI的状态寄存器进行监控,可以确保正确且高效地进行数据的发送和接收。
SPI(Serial Peripheral Interface)工作原理基于主从模式,其核心在于主机(Master)控制通信过程,包括时钟信号的产生和数据传输的时序。以下是SPI工作原理的简要描述:
通信时序:
主机写操作:
主机读操作:
总结起来,SPI协议允许主机在单个时钟周期内同时进行写和读操作,实现了全双工通信。虽然在主机写操作时不关注从机的返回数据,但在进行读操作时,即使主机不关心从机的数据内容,也需要发送一个“触发”信号(通常是空数据或特定指令)来促使从机开始发送数据。
SPI(Serial Peripheral Interface)有四种工作模式,这些模式由两个参数决定:时钟极性(CPOL)和时钟相位(CPHA)。下面是SPI四种工作模式的详细解释:
时钟极性(CPOL):
时钟相位(CPHA):
结合CPOL和CPHA的不同设置,SPI共有四种工作模式:
每种模式都会影响数据与时钟信号的相对位置,但无论哪种模式,SPI通信的基本原理都是在主设备的控制下,通过共享的时钟信号,在每个时钟周期内逐位传输数据。
时钟极性(CPOL)和时钟相位(CPHA)是SPI通信协议中用来描述数据采样和时钟信号关系的两个重要参数。下面我们将详细讲解并通过一个象限图来分析这两种参数对SPI时序的影响。
时钟极性(CPOL):
时钟相位(CPHA):
现在,我们可以用一个二维象限图来表示这两种参数的不同组合:
| CPOL=0 | CPOL=1
-------|-------------|-------------
CPHA=0 | Mode 0 | Mode 3
| (Sample at falling edge, latch at rising edge) |
-------|-------------|-------------
CPHA=1 | Mode 1 | Mode 2
| (Sample at rising edge, latch at falling edge) |
Mode 0(CPOL=0, CPHA=0):
Mode 1(CPOL=0, CPHA=1):
Mode 2(CPOL=1, CPHA=0):
Mode 3(CPOL=1, CPHA=1):
综上所述,通过设定不同的CPOL和CPHA组合,可以实现SPI通信的四种不同的时序模式,以适应不同设备的需求。在实际应用中,必须确保主从设备间的SPI模式匹配,才能保证正确可靠的数据传输。
SPI(Serial Peripheral Interface)是一种常用的串行通信协议,在嵌入式系统和微控制器中广泛使用。下面是对您提到的几个关键SPI寄存器的详细说明:
SPI_CR1(SPI Control Register 1)
MSTR
:主/从模式选择,决定SPI设备作为主设备还是从设备。BR[2:0]
或类似字段:波特率预分频设置,用于确定SPI总线的时钟频率。CPOL
和 CPHA
:用来设置时钟极性和相位,对应之前所描述的四种工作模式(Mode 0至Mode 3)。SSM
和 SSI
:硬件/软件管理片选信号的选择及内建的NSS信号的状态。LSBFIRST
:字节内的数据传输顺序选择,低位先传(LSB-first)或高位先传(MSB-first)。SPE
:SPI外设使能位,用于开启或关闭SPI模块。SPI_SR(SPI Status Register)
TXE
:Transmit Buffer Empty,发送缓冲器空标志,当SPI准备好接受新的待发送数据时置位。RXNE
:Receive Buffer Not Empty,接收缓冲器非空标志,当SPI接收到有效数据并存放在接收缓冲器中时置位。BSY
:Busy,忙标志,SPI正在进行数据传输时置位。SPI_DR(SPI Data Register)
通过配置和监控这些寄存器,开发人员能够有效地控制SPI接口的操作和数据传输。
对于STM32H7系列微控制器来说,SPI相关的寄存器略有不同,但基本功能相似。以下是针对H7系列的SPI寄存器介绍:
SPI时钟源选择:
RCC_D2CCIP1R
(RCC Domain 2 Configurable Clocks Peripherals 1 Register)使能SPI:
SPI_CR1
(SPI Control Register 1)SPIEN
位,可以开启或关闭指定SPI外设。配置SPI工作参数:
寄存器:SPI_CFG1
(SPI Configuration Register 1)
功能:用于配置SPI的传输速率、帧格式等基本参数。
寄存器:SPI_CFG2
(SPI Configuration Register 2)
功能:进一步配置SPI的工作模式(例如CPOL、CPHA等)、NSS管脚的行为(硬件或软件控制)、数据传输顺序(MSB或LSB优先)等。
选择SPI模式:
SPI_I2SCFGR
(SPI/I2S Configuration Register)SPI_I2SCFGR
寄存器用于选择SPI模式和其他相关配置。查看SPI传输状态:
SPI_SR
(SPI Status Register)TXE
、RXNE
等,可以了解到SPI当前的传输状态。发送数据寄存器:
SPI_TXDR
(SPI Transmit Data Register)接收数据寄存器:
SPI_RXDR
(SPI Receive Data Register)在STM32H7系列中,SPI外设可能集成了FIFO(First-In-First-Out)缓冲区,从而提高了数据传输的效率和性能。
SPI相关HAL库驱动函数及其功能介绍:
__HAL_RCC_SPIx_CLK_ENABLE(…)
HAL_SPI_Init(…)
HAL_SPI_MspInit(…)
HAL_SPI_Transmit(…)
HAL_SPI_Receive(…)
HAL_SPI_TransmitReceive(…)
__HAL_SPI_ENABLE(…)
__HAL_SPI_DISABLE(…)
对于STM32H7系列微控制器来说,除了基本的HAL库函数外,确实需要使HAL_RCCEx_PeriphCLKConfig函数来配置SPI的时钟源。这是因为H7系列拥有更丰富的时钟树结构,SPI时钟可以源自不同的振荡器或者PLL分频输出。
此外,STM32F7和STM32H7系列还提供了额外的SPI功能选项:
NSS管理:
SPI_CR2寄存器中的NSSP位可以配置NSS信号是否由硬件还是软件管理,这在SPI作为主设备时特别有用。
CRC功能:
SPI_CR1和SPI_CR2寄存器中的相关位可以启用或禁用SPI的CRC(循环冗余校验)功能,用于数据包的错误检测。
IO交换功能:
对于某些应用,可能需要在SPI通信中交换MOSI和MISO引脚的功能,H7系列的SPI提供了IO交换功能,可以通过SPI_I2SCFGR寄存器中的IOFFSET位来配置。
在使用HAL库进行SPI驱动开发时,开发者可以根据实际项目需求,利用这些附加功能来优化SPI接口的性能和灵活性。
XIP技术:https://blog.csdn.net/ffdia/article/details/87437872
NOR Flash 和 NAND Flash 的特点和应用举例:
NOR Flash:
特点:
应用举例:
NAND Flash:
特点:
应用举例:
NM25Q128是一款128Mb(16MB)容量的NOR型串行闪存芯片,专为需要高速、可靠数据存储的应用而设计。该器件通过SPI(Serial Peripheral Interface)接口与主控设备通信,提供了一种节省引脚、易于集成到系统中的存储方案。
特点概述:
接口信号说明:
总的来说,NM25Q128因其高密度、高速度、易用性和耐用性等特点,被广泛应用于嵌入式系统、固件存储、微控制器的代码存储、数据记录等多种场合。
NM25Q128的存储结构按照地址范围、页、扇区和块进行组织,其具体划分如下:
地址范围:NM25Q128的地址范围是从0x00000000到0x00FFFFFF,共计16,777,216个地址,对应128Mbit(16,777,216比特)或16MByte(16,777,216字节)的存储容量。
存储单元划分:
整体结构:整个NM25Q128由256个这样的块组成,因此其总容量为256 * 65,536字节 = 16,777,216字节(即16MByte)。
这种分层结构使得NM25Q128在进行数据管理和维护时具有良好的灵活性,最小擦除单位为一个扇区,数据读写则以字节为单位。在进行擦除操作时,必须以扇区为单位进行,这样有助于提升使用寿命并简化固件升级等操作。
NM25Q128或者其他NOR Flash常用的指令还包括但不限于以下几种:
指令(十六进制):0x06
名称:写使能(Write Enable)
作用:在对NOR Flash进行任何写入操作(包括页写和擦除)之前,必须先发送这条指令,使得NOR Flash进入写入允许状态。
指令(十六进制):0x05
名称:读状态寄存器1(Read Status Register 1)
作用:用于读取NOR Flash的状态寄存器1,确认NOR Flash是否处于空闲状态,是否准备好进行下一步操作,特别是擦除操作之前需要确认设备是否已完成上一次操作。
指令(十六进制):0x03
名称:读数据(Read Data)
作用:用于从NOR Flash中读取数据,通过指定地址读取连续的数据流。
指令(十六进制):0x02
名称:页编程(Page Program)
作用:用于向NOR Flash的指定地址开始的连续256字节区域写入数据。注意,写入数据必须按照页的边界进行,不能跨越页的边界。
指令(十六进制):0x20
名称:扇区擦除(Sector Erase)
作用:对NOR Flash中最小擦除单位(扇区)进行擦除操作,通常NM25Q128的一个扇区大小为4096字节(4KB)。
除此之外,还有其他的指令,例如:
指令(十六进制):0xD8
名称:块擦除(Bulk Erase)
作用:擦除整个芯片的所有数据,对于NM25Q128来说,一次性擦除所有256个块。
指令(十六进制):0xB0~0xBF
名称:读取标识符(Read Manufacturer and Device ID)
作用:读取制造商ID和设备ID,用于验证和识别具体的NOR Flash型号。
指令(十六进制):0x04
名称:写禁止(Write Disable)
作用:结束写入操作序列,防止意外的写入动作发生,使NOR Flash退出写入允许状态。
NM25Q128的读、擦除、写操作时序如下:
写使能(Write Enable)
读状态寄存器1(Read Status Register 1)
读数据时序(Read Data Bytes)
页写时序(Page Program)
扇区擦除时序(Sector Erase)
在实际操作过程中,还需注意地址和数据的传输格式以及相关的时钟同步,确保严格按照器件数据手册的要求进行操作。此外,某些操作完成后,建议发送写禁止指令(0x04H)以防止意外的写操作。
在执行诸如Page Program(页写)、Sector Erase(扇区擦除)、Block Erase(块擦除)和Chip Erase(整片擦除)以及Write Status Register(写状态寄存器)等操作之前,必须先向NOR Flash发送Write Enable(写使能)指令(0x06H)。这个指令的作用是让NOR Flash进入可写状态,只有在写使能之后,才能进行后续的写入或擦除操作。
当写使能指令发出后,NOR Flash内部的相关控制位会被设置,允许接下来的写入或擦除指令被执行。完成相应的写入或擦除操作后,通常需要发送Write Disable(写禁止)指令以取消写使能状态,防止在不需要写入的时候误操作导致数据损坏。写禁止指令通常为0x04H。
NM25Q128的读操作步骤可以细化如下:
发送读命令
发送地址
读取数据
总之,NM25Q128的读操作流程是先发送读指令,接着发送目标地址,然后通过发送无效数据字节触发并连续读取存储器中的数据。在整个读取过程中,务必确保时钟信号的正确同步,以保证数据的准确传输。
NM25Q128擦除扇区的具体步骤如下:
发送写使能命令
等待空闲
发送擦除扇区命令
发送地址
等待空闲
需要注意的是,整个擦除操作期间需要确保时钟信号SCK的正确同步,并且在擦除操作结束后,最好发送写禁止命令(0x04H)以避免意外的写操作。此外,在实际应用中,请参照NM25Q128的具体数据手册,遵循正确的时序和操作规范。
NM25Q128写操作步骤(此处假设目标区域已经经过擦除):
擦除扇区(20H)
发送写使能命令
发送页写命令
发送地址
发送数据
等待空闲
在实际应用中,一定要确保写入数据前目标区域已被擦除,并且在写入操作结束后,推荐发送写禁止命令(0x04H)以避免不必要的写操作。同时,严格遵守NM25Q128的数据手册中规定的时序要求。
NOR FLASH基于SPI接口的基本驱动步骤可以概括为以下几点:
SPI工作参数配置初始化
HAL_SPI_Init()
函数对SPI外设进行初始化,设置工作模式(CPOL和CPHA)、时钟速度、数据帧格式(8位、16位等)、NSS管理方式等参数。使能SPI时钟和初始化相关引脚
__HAL_RCC_SPIx_CLK_ENABLE()
函数启用SPI外设的时钟。HAL_SPI_MspInit()
函数内完成,而是在初始化SPI之前手动配置GPIO的AF(Alternate Function)寄存器。// 示例代码(伪代码)
GPIO_InitStruct.Pin = GPIO_PIN_...; // 设置SPI对应引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 设置为复用推挽输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 如果不需要上下拉电阻,设置为无上拉下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 设置GPIO速度
GPIO_InitStruct.Alternate = GPIO_AF_SPIx; // 设置对应SPI外设的复用功能
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); // 初始化GPIO引脚
使能SPI
__HAL_SPI_ENABLE(&hspi)
函数使能SPI外设,这里的hspi
是指向SPI_HandleTypeDef结构体的指针。SPI传输数据
HAL_SPI_Transmit(&hspi, tx_buffer, size, timeout)
函数发送数据,tx_buffer是发送缓冲区,size是发送数据的长度,timeout是超时时间。HAL_SPI_Receive(&hspi, rx_buffer, size, timeout)
函数接收数据,rx_buffer是接收缓冲区。HAL_SPI_TransmitReceive(&hspi, tx_buffer, rx_buffer, size, timeout)
函数执行全双工传输。设置SPI传输速度
HAL_SPI_Init()
函数里通过配置SPI_InitTypeDef结构体中的BaudRatePrescaler
字段来间接设置SPI时钟速度。请注意,以上步骤仅为基本流程指导,具体实现细节会因微控制器系列和HAL库版本的不同而有所差异,务必查阅对应MCU的手册和HAL库API文档。
NM25Q128 NOR Flash的驱动步骤可以归纳为以下详细步骤:
初始化片选引脚与SPI接口
HAL_SPI_Init()
进行初始化。NM25Q128 读取
NM25Q128 扇区擦除
NM25Q128 写入
NOR FLASH驱动注意事项:
在实际应用中,应根据NM25Q128的数据手册和所使用的HAL库函数来精确实施上述步骤。
spi.c
#include "./BSP/SPI/spi.h"
SPI_HandleTypeDef g_spi2_handler; /* SPI2句柄 */
void spi2_init(void)
{
g_spi2_handler.Instance = SPI2_SPI; /* SPI2 */
g_spi2_handler.Init.Mode = SPI_MODE_MASTER; /* 设置SPI模式(主机模式) */
g_spi2_handler.Init.Direction = SPI_DIRECTION_2LINES; /* 设置SPI工作方式(全双工) */
g_spi2_handler.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据格式(8bit长度) */
g_spi2_handler.Init.CLKPolarity = SPI_POLARITY_HIGH; /* 设置时钟极性(CPOL = 1) */
g_spi2_handler.Init.CLKPhase = SPI_PHASE_2EDGE; /* 设置时钟相位(CPHA = 1) */
g_spi2_handler.Init.NSS = SPI_NSS_SOFT; /* 设置片选方式(软件片选,自定义GPIO) */
g_spi2_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 设置SPI时钟波特率分频(256分频) */
g_spi2_handler.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 设置大小端模式(MSB高位在前) */
g_spi2_handler.Init.TIMode = SPI_TIMODE_DISABLE; /* 设置帧格式(关闭TI模式) */
g_spi2_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 设置CRC校验(关闭CRC校验) */
g_spi2_handler.Init.CRCPolynomial = 7; /* 设置CRC校验多项式(范围:1~65535) */
HAL_SPI_Init(&g_spi2_handler);
}
/**
* @brief SPI2底层驱动,时钟使能,引脚配置
* @note 此函数会被HAL_SPI_Init()调用
* @param hspi:SPI句柄
* @retval 无
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
SPI2_SPI_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
if (hspi->Instance == SPI2_SPI)
{
SPI2_SCK_GPIO_CLK_ENABLE(); /* SPI2_SCK脚时钟使能 */
SPI2_MISO_GPIO_CLK_ENABLE(); /* SPI2_MISO脚时钟使能 */
SPI2_MOSI_GPIO_CLK_ENABLE(); /* SPI2_MOSI脚时钟使能 */
/* SCK引脚模式设置(复用输出) */
gpio_init_struct.Pin = SPI2_SCK_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI2_SCK_GPIO_PORT, &gpio_init_struct);
/* MISO引脚模式设置(复用输出) */
gpio_init_struct.Pin = SPI2_MISO_GPIO_PIN;
HAL_GPIO_Init(SPI2_MISO_GPIO_PORT, &gpio_init_struct);
/* MOSI引脚模式设置(复用输出) */
gpio_init_struct.Pin = SPI2_MOSI_GPIO_PIN;
HAL_GPIO_Init(SPI2_MOSI_GPIO_PORT, &gpio_init_struct);
}
}
uint8_t spi2_read_write_byte(uint8_t data)
{
uint8_t rec_data = 0;
HAL_SPI_TransmitReceive(&g_spi2_handler, &data, &rec_data, 1, 1000);
return rec_data;
}
spi.h
#ifndef __SPI_H
#define __SPI_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* SPI2 引脚 定义 */
#define SPI2_SCK_GPIO_PORT GPIOB
#define SPI2_SCK_GPIO_PIN GPIO_PIN_13
#define SPI2_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MISO_GPIO_PORT GPIOB
#define SPI2_MISO_GPIO_PIN GPIO_PIN_14
#define SPI2_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MOSI_GPIO_PORT GPIOB
#define SPI2_MOSI_GPIO_PIN GPIO_PIN_15
#define SPI2_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/* SPI2相关定义 */
#define SPI2_SPI SPI2
#define SPI2_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI2_CLK_ENABLE(); }while(0) /* SPI2时钟使能 */
/******************************************************************************************/
void spi2_init(void);
uint8_t spi2_read_write_byte(uint8_t data);
#endif
norflash.c
#include "./BSP/SPI/spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"
void norflash_init(void)
{
NORFLASH_CS_GPIO_CLK_ENABLE(); /* NORFLASH CS脚 时钟使能 */
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct); /* SCK引脚模式设置(复用输出) */
spi2_init();
spi2_read_write_byte(0xFF); /* 清除DR的作用 */
NORFLASH_CS(1);
}
uint8_t norflash_rd_sr1(void)
{
uint8_t rec_data = 0;
NORFLASH_CS(0);
spi2_read_write_byte(0x05); /* 读状态寄存器1 */
rec_data = spi2_read_write_byte(0xFF);
NORFLASH_CS(1);
return rec_data;
}
uint8_t norflash_read_data(uint32_t addr)
{
uint8_t rec_data = 0;
NORFLASH_CS(0);
/* 1 发送读命令 */
spi2_read_write_byte(0x03);
/* 2 发送地址 */
spi2_read_write_byte(addr >> 16);
spi2_read_write_byte(addr >> 8);
spi2_read_write_byte(addr);
/* 3 读取数据 */
rec_data = spi2_read_write_byte(0xFF);
NORFLASH_CS(1);
return rec_data;
}
void norflash_erase_sector(uint32_t addr)
{
/* 1 写使能 */
NORFLASH_CS(0);
spi2_read_write_byte(0x06);
NORFLASH_CS(1);
/* 2 等待空闲 */
while(norflash_rd_sr1()&0x01);
/* 3 发送扇区擦除指令 */
NORFLASH_CS(0);
spi2_read_write_byte(0x20);
/* 4 发送地址 */
spi2_read_write_byte(addr >> 16);
spi2_read_write_byte(addr >> 8);
spi2_read_write_byte(addr);
NORFLASH_CS(1);
/* 5 等待空闲 */
while(norflash_rd_sr1()&0x01);
}
void norflash_write_page(uint8_t data, uint32_t addr)
{
/* 1 擦除扇区 */
norflash_erase_sector(addr);
/* 2 写使能 */
NORFLASH_CS(0);
spi2_read_write_byte(0x06);
NORFLASH_CS(1);
/* 3 发送页写指令 */
NORFLASH_CS(0);
spi2_read_write_byte(0x02);
/* 4 发送地址 */
spi2_read_write_byte(addr >> 16);
spi2_read_write_byte(addr >> 8);
spi2_read_write_byte(addr);
/* 5 要写入的数据 */
spi2_read_write_byte(data);
NORFLASH_CS(1);
/* 6 等待写入完成(等待空闲) */
while(norflash_rd_sr1()&0x01);
}
norflash.h
#ifndef __norflash_H
#define __norflash_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */
#define NORFLASH_CS_GPIO_PORT GPIOB
#define NORFLASH_CS_GPIO_PIN GPIO_PIN_12
#define NORFLASH_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/* NORFLASH 片选信号 */
#define NORFLASH_CS(x) do{ x ? \
HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
void norflash_init(void);
uint8_t norflash_rd_sr1(void);
uint8_t norflash_read_data(uint32_t addr);
void norflash_erase_sector(uint32_t addr);
void norflash_write_page(uint8_t data, uint32_t addr);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/NORFLASH/norflash.h"
int main(void)
{
uint8_t key;
uint16_t i = 0;
uint8_t rec_data = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(72); /* 初始化USMART */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
norflash_init();
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES) /* KEY1按下,写入 */
{
norflash_write_page('5', 0x123457); /* 地址范围0~0xFFFFFF */
printf("write finish \r\n");
}
if (key == KEY0_PRES) /* KEY0按下,读取数据 */
{
rec_data = norflash_read_data(0x123456);
printf("read data : %c \r\n", rec_data);
}
i++;
if (i == 20)
{
LED0_TOGGLE(); /* LED0闪烁 */
i = 0;
}
delay_ms(10);
}
}
spi.c
#include "./BSP/SPI/spi.h"
SPI_HandleTypeDef g_spi2_handler; /* SPI2句柄 */
/**
* @brief SPI初始化代码
* @note 主机模式,8位数据,禁止硬件片选
* @param 无
* @retval 无
*/
void spi2_init(void)
{
SPI2_SPI_CLK_ENABLE(); /* SPI2时钟使能 */
g_spi2_handler.Instance = SPI2_SPI; /* SPI2 */
g_spi2_handler.Init.Mode = SPI_MODE_MASTER; /* 设置SPI工作模式,设置为主模式 */
g_spi2_handler.Init.Direction = SPI_DIRECTION_2LINES; /* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
g_spi2_handler.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置SPI的数据大小:SPI发送接收8位帧结构 */
g_spi2_handler.Init.CLKPolarity = SPI_POLARITY_HIGH; /* 串行同步时钟的空闲状态为高电平 */
g_spi2_handler.Init.CLKPhase = SPI_PHASE_2EDGE; /* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
g_spi2_handler.Init.NSS = SPI_NSS_SOFT; /* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
g_spi2_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 定义波特率预分频的值:波特率预分频值为256 */
g_spi2_handler.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
g_spi2_handler.Init.TIMode = SPI_TIMODE_DISABLE; /* 关闭TI模式 */
g_spi2_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 关闭硬件CRC校验 */
g_spi2_handler.Init.CRCPolynomial = 7; /* CRC值计算的多项式 */
HAL_SPI_Init(&g_spi2_handler); /* 初始化 */
__HAL_SPI_ENABLE(&g_spi2_handler); /* 使能SPI2 */
spi2_read_write_byte(0Xff); /* 启动传输, 实际上就是产生8个时钟脉冲, 达到清空DR的作用, 非必需 */
}
/**
* @brief SPI2底层驱动,时钟使能,引脚配置
* @note 此函数会被HAL_SPI_Init()调用
* @param hspi:SPI句柄
* @retval 无
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Initure;
if (hspi->Instance == SPI2_SPI)
{
SPI2_SCK_GPIO_CLK_ENABLE(); /* SPI2_SCK脚时钟使能 */
SPI2_MISO_GPIO_CLK_ENABLE(); /* SPI2_MISO脚时钟使能 */
SPI2_MOSI_GPIO_CLK_ENABLE(); /* SPI2_MOSI脚时钟使能 */
/* SCK引脚模式设置(复用输出) */
GPIO_Initure.Pin = SPI2_SCK_GPIO_PIN;
GPIO_Initure.Mode = GPIO_MODE_AF_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI2_SCK_GPIO_PORT, &GPIO_Initure);
/* MISO引脚模式设置(复用输出) */
GPIO_Initure.Pin = SPI2_MISO_GPIO_PIN;
HAL_GPIO_Init(SPI2_MISO_GPIO_PORT, &GPIO_Initure);
/* MOSI引脚模式设置(复用输出) */
GPIO_Initure.Pin = SPI2_MOSI_GPIO_PIN;
HAL_GPIO_Init(SPI2_MOSI_GPIO_PORT, &GPIO_Initure);
}
}
/**
* @brief SPI2速度设置函数
* @note SPI2时钟选择来自APB1, 即PCLK1, 为36Mhz
* SPI速度 = PCLK1 / 2^(speed + 1)
* @param speed : SPI2时钟分频系数
取值为SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_2 256
* @retval 无
*/
void spi2_set_speed(uint8_t speed)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(speed)); /* 判断有效性 */
__HAL_SPI_DISABLE(&g_spi2_handler); /* 关闭SPI */
g_spi2_handler.Instance->CR1 &= 0XFFC7; /* 位3-5清零,用来设置波特率 */
g_spi2_handler.Instance->CR1 |= speed << 3; /* 设置SPI速度 */
__HAL_SPI_ENABLE(&g_spi2_handler); /* 使能SPI */
}
/**
* @brief SPI2读写一个字节数据
* @param txdata : 要发送的数据(1字节)
* @retval 接收到的数据(1字节)
*/
uint8_t spi2_read_write_byte(uint8_t txdata)
{
uint8_t rxdata;
HAL_SPI_TransmitReceive(&g_spi2_handler, &txdata, &rxdata, 1, 1000);
return rxdata; /* 返回收到的数据 */
}
spi.h
#ifndef __SPI_H
#define __SPI_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* SPI2 引脚 定义 */
#define SPI2_SCK_GPIO_PORT GPIOB
#define SPI2_SCK_GPIO_PIN GPIO_PIN_13
#define SPI2_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MISO_GPIO_PORT GPIOB
#define SPI2_MISO_GPIO_PIN GPIO_PIN_14
#define SPI2_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MOSI_GPIO_PORT GPIOB
#define SPI2_MOSI_GPIO_PIN GPIO_PIN_15
#define SPI2_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/* SPI2相关定义 */
#define SPI2_SPI SPI2
#define SPI2_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI2_CLK_ENABLE(); }while(0) /* SPI2时钟使能 */
/******************************************************************************************/
/* SPI总线速度设置 */
#define SPI_SPEED_2 0
#define SPI_SPEED_4 1
#define SPI_SPEED_8 2
#define SPI_SPEED_16 3
#define SPI_SPEED_32 4
#define SPI_SPEED_64 5
#define SPI_SPEED_128 6
#define SPI_SPEED_256 7
void spi2_init(void);
void spi2_set_speed(uint8_t speed);
uint8_t spi2_read_write_byte(uint8_t txdata);
#endif
norflash.c
#include "./BSP/SPI/spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/NORFLASH/norflash.h"
uint16_t g_norflash_type = NM25Q128; /* 默认是NM25Q128 */
/**
* @brief 初始化SPI NOR FLASH
* @param 无
* @retval 无
*/
void norflash_init(void)
{
uint8_t temp;
NORFLASH_CS_GPIO_CLK_ENABLE(); /* NORFLASH CS脚 时钟使能 */
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct); /* CS引脚模式设置(复用输出) */
NORFLASH_CS(1); /* 取消片选 */
spi2_init(); /* 初始化SPI2 */
spi2_set_speed(SPI_SPEED_2); /* SPI2 切换到高速状态 18Mhz */
g_norflash_type = norflash_read_id(); /* 读取FLASH ID. */
if (g_norflash_type == W25Q256) /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */
{
temp = norflash_read_sr(3); /* 读取状态寄存器3,判断地址模式 */
if ((temp & 0X01) == 0) /* 如果不是4字节地址模式,则进入4字节地址模式 */
{
norflash_write_enable(); /* 写使能 */
temp |= 1 << 1; /* ADP=1, 上电4字节地址模式 */
norflash_write_sr(3, temp); /* 写SR3 */
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_Enable4ByteAddr); /* 使能4字节地址指令 */
NORFLASH_CS(1);
}
}
//printf("ID:%x\r\n", g_norflash_type);
}
/**
* @brief 等待空闲
* @param 无
* @retval 无
*/
static void norflash_wait_busy(void)
{
while ((norflash_read_sr(1) & 0x01) == 0x01); /* 等待BUSY位清空 */
}
/**
* @brief 25QXX写使能
* @note 将S1寄存器的WEL置位
* @param 无
* @retval 无
*/
void norflash_write_enable(void)
{
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_WriteEnable); /* 发送写使能 */
NORFLASH_CS(1);
}
/**
* @brief 25QXX发送地址
* @note 根据芯片型号的不同, 发送24ibt / 32bit地址
* @param address : 要发送的地址
* @retval 无
*/
static void norflash_send_address(uint32_t address)
{
if (g_norflash_type == W25Q256) /* 只有W25Q256支持4字节地址模式 */
{
spi2_read_write_byte((uint8_t)((address)>>24)); /* 发送 bit31 ~ bit24 地址 */
}
spi2_read_write_byte((uint8_t)((address)>>16)); /* 发送 bit23 ~ bit16 地址 */
spi2_read_write_byte((uint8_t)((address)>>8)); /* 发送 bit15 ~ bit8 地址 */
spi2_read_write_byte((uint8_t)address); /* 发送 bit7 ~ bit0 地址 */
}
/**
* @brief 读取25QXX的状态寄存器,25QXX一共有3个状态寄存器
* @note 状态寄存器1:
* BIT7 6 5 4 3 2 1 0
* SPR RV TB BP2 BP1 BP0 WEL BUSY
* SPR:默认0,状态寄存器保护位,配合WP使用
* TB,BP2,BP1,BP0:FLASH区域写保护设置
* WEL:写使能锁定
* BUSY:忙标记位(1,忙;0,空闲)
* 默认:0x00
*
* 状态寄存器2:
* BIT7 6 5 4 3 2 1 0
* SUS CMP LB3 LB2 LB1 (R) QE SRP1
*
* 状态寄存器3:
* BIT7 6 5 4 3 2 1 0
* HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
*
* @param regno: 状态寄存器号,范:1~3
* @retval 状态寄存器值
*/
uint8_t norflash_read_sr(uint8_t regno)
{
uint8_t byte = 0, command = 0;
switch (regno)
{
case 1:
command = FLASH_ReadStatusReg1; /* 读状态寄存器1指令 */
break;
case 2:
command = FLASH_ReadStatusReg2; /* 读状态寄存器2指令 */
break;
case 3:
command = FLASH_ReadStatusReg3; /* 读状态寄存器3指令 */
break;
default:
command = FLASH_ReadStatusReg1;
break;
}
NORFLASH_CS(0);
spi2_read_write_byte(command); /* 发送读寄存器命令 */
byte = spi2_read_write_byte(0Xff); /* 读取一个字节 */
NORFLASH_CS(1);
return byte;
}
/**
* @brief 写25QXX状态寄存器
* @note 寄存器说明见norflash_read_sr函数说明
* @param regno: 状态寄存器号,范:1~3
* @param sr : 要写入状态寄存器的值
* @retval 无
*/
void norflash_write_sr(uint8_t regno, uint8_t sr)
{
uint8_t command = 0;
switch (regno)
{
case 1:
command = FLASH_WriteStatusReg1; /* 写状态寄存器1指令 */
break;
case 2:
command = FLASH_WriteStatusReg2; /* 写状态寄存器2指令 */
break;
case 3:
command = FLASH_WriteStatusReg3; /* 写状态寄存器3指令 */
break;
default:
command = FLASH_WriteStatusReg1;
break;
}
NORFLASH_CS(0);
spi2_read_write_byte(command); /* 发送读寄存器命令 */
spi2_read_write_byte(sr); /* 写入一个字节 */
NORFLASH_CS(1);
}
/**
* @brief 读取芯片ID
* @param 无
* @retval FLASH芯片ID
* @note 芯片ID列表见: norflash.h, 芯片列表部分
*/
uint16_t norflash_read_id(void)
{
uint16_t deviceid;
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_ManufactDeviceID); /* 发送读 ID 命令 */
spi2_read_write_byte(0); /* 写入一个字节 */
spi2_read_write_byte(0);
spi2_read_write_byte(0);
deviceid = spi2_read_write_byte(0xFF) << 8; /* 读取高8位字节 */
deviceid |= spi2_read_write_byte(0xFF); /* 读取低8位字节 */
NORFLASH_CS(1);
return deviceid;
}
/**
* @brief 读取SPI FLASH
* @note 在指定地址开始读取指定长度的数据
* @param pbuf : 数据存储区
* @param addr : 开始读取的地址(最大32bit)
* @param datalen : 要读取的字节数(最大65535)
* @retval 无
*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_ReadData); /* 发送读取命令 */
norflash_send_address(addr); /* 发送地址 */
for(i = 0; i < datalen; i++)
{
pbuf[i] = spi2_read_write_byte(0XFF); /* 循环读取 */
}
NORFLASH_CS(1);
}
/**
* @brief SPI在一页(0~65535)内写入少于256个字节的数据
* @note 在指定地址开始写入最大256字节的数据
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
* @retval 无
*/
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
norflash_write_enable(); /* 写使能 */
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_PageProgram); /* 发送写页命令 */
norflash_send_address(addr); /* 发送地址 */
for(i = 0; i < datalen; i++)
{
spi2_read_write_byte(pbuf[i]); /* 循环写入数据 */
}
NORFLASH_CS(1);
norflash_wait_busy(); /* 等待写入结束 */
}
/**
* @brief 无检验写SPI FLASH
* @note 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
* 具有自动换页功能
* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; /* 单页剩余的字节数 */
if (datalen <= pageremain) /* 不大于256个字节 */
{
pageremain = datalen;
}
while (1)
{
/* 当写入字节比页内剩余地址还少的时候, 一次性写完
* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
*/
norflash_write_page(pbuf, addr, pageremain);
if (datalen == pageremain) /* 写入结束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */
addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */
datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */
{
pageremain = 256; /* 一次可以写入256个字节 */
}
else /* 剩余数据小于一页,可以一次写完 */
{
pageremain = datalen; /* 不够256个字节了 */
}
}
}
}
/**
* @brief 写SPI FLASH
* @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
* SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
* 擦除的最小单位为Sector.
*
* @param pbuf : 数据存储区
* @param addr : 开始写入的地址(最大32bit)
* @param datalen : 要写入的字节数(最大65535)
* @retval 无
*/
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *norflash_buf;
norflash_buf = g_norflash_buf;
secpos = addr / 4096; /* 扇区地址 */
secoff = addr % 4096; /* 在扇区内的偏移 */
secremain = 4096 - secoff; /* 扇区剩余空间大小 */
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
if (datalen <= secremain)
{
secremain = datalen; /* 不大于4096个字节 */
}
while (1)
{
norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */
for (i = 0; i < secremain; i++) /* 校验数据 */
{
if (norflash_buf[secoff + i] != 0XFF)
{
break; /* 需要擦除, 直接退出for循环 */
}
}
if (i < secremain) /* 需要擦除 */
{
norflash_erase_sector(secpos); /* 擦除这个扇区 */
for (i = 0; i < secremain; i++) /* 复制 */
{
norflash_buf[i + secoff] = pbuf[i];
}
norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 写入整个扇区 */
}
else /* 写已经擦除了的,直接写入扇区剩余区间. */
{
norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
}
if (datalen == secremain)
{
break; /* 写入结束了 */
}
else /* 写入未结束 */
{
secpos++; /* 扇区地址增1 */
secoff = 0; /* 偏移位置为0 */
pbuf += secremain; /* 指针偏移 */
addr += secremain; /* 写地址偏移 */
datalen -= secremain; /* 字节数递减 */
if (datalen > 4096)
{
secremain = 4096; /* 下一个扇区还是写不完 */
}
else
{
secremain = datalen;/* 下一个扇区可以写完了 */
}
}
}
}
/**
* @brief 擦除整个芯片
* @note 等待时间超长...
* @param 无
* @retval 无
*/
void norflash_erase_chip(void)
{
norflash_write_enable(); /* 写使能 */
norflash_wait_busy(); /* 等待空闲 */
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_ChipErase); /* 发送读寄存器命令 */
NORFLASH_CS(1);
norflash_wait_busy(); /* 等待芯片擦除结束 */
}
/**
* @brief 擦除一个扇区
* @note 注意,这里是扇区地址,不是字节地址!!
* 擦除一个扇区的最少时间:150ms
*
* @param saddr : 扇区地址 根据实际容量设置
* @retval 无
*/
void norflash_erase_sector(uint32_t saddr)
{
//printf("fe:%x\r\n", saddr); /* 监视falsh擦除情况,测试用 */
saddr *= 4096;
norflash_write_enable(); /* 写使能 */
norflash_wait_busy(); /* 等待空闲 */
NORFLASH_CS(0);
spi2_read_write_byte(FLASH_SectorErase); /* 发送写页命令 */
norflash_send_address(saddr); /* 发送地址 */
NORFLASH_CS(1);
norflash_wait_busy(); /* 等待扇区擦除完成 */
}
norflash.h
#ifndef __norflash_H
#define __norflash_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */
#define NORFLASH_CS_GPIO_PORT GPIOB
#define NORFLASH_CS_GPIO_PIN GPIO_PIN_12
#define NORFLASH_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/* NORFLASH 片选信号 */
#define NORFLASH_CS(x) do{ x ? \
HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/* FLASH芯片列表 */
#define W25Q80 0XEF13 /* W25Q80 芯片ID */
#define W25Q16 0XEF14 /* W25Q16 芯片ID */
#define W25Q32 0XEF15 /* W25Q32 芯片ID */
#define W25Q64 0XEF16 /* W25Q64 芯片ID */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
#define W25Q256 0XEF18 /* W25Q256 芯片ID */
#define BY25Q64 0X6816 /* BY25Q64 芯片ID */
#define BY25Q128 0X6817 /* BY25Q128 芯片ID */
#define NM25Q64 0X5216 /* NM25Q64 芯片ID */
#define NM25Q128 0X5217 /* NM25Q128 芯片ID */
extern uint16_t norflash_TYPE; /* 定义FLASH芯片型号 */
/* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_WriteDisable 0x04
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadStatusReg2 0x35
#define FLASH_ReadStatusReg3 0x15
#define FLASH_WriteStatusReg1 0x01
#define FLASH_WriteStatusReg2 0x31
#define FLASH_WriteStatusReg3 0x11
#define FLASH_ReadData 0x03
#define FLASH_FastReadData 0x0B
#define FLASH_FastReadDual 0x3B
#define FLASH_FastReadQuad 0xEB
#define FLASH_PageProgram 0x02
#define FLASH_PageProgramQuad 0x32
#define FLASH_BlockErase 0xD8
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_PowerDown 0xB9
#define FLASH_ReleasePowerDown 0xAB
#define FLASH_DeviceID 0xAB
#define FLASH_ManufactDeviceID 0x90
#define FLASH_JedecDeviceID 0x9F
#define FLASH_Enable4ByteAddr 0xB7
#define FLASH_Exit4ByteAddr 0xE9
#define FLASH_SetReadParam 0xC0
#define FLASH_EnterQPIMode 0x38
#define FLASH_ExitQPIMode 0xFF
/* 静态函数 */
static void norflash_wait_busy(void); /* 等待空闲 */
static void norflash_send_address(uint32_t address);/* 发送地址 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入page */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 */
/* 普通函数 */
void norflash_init(void); /* 初始化25QXX */
uint16_t norflash_read_id(void); /* 读取FLASH ID */
void norflash_write_enable(void); /* 写使能 */
uint8_t norflash_read_sr(uint8_t regno); /* 读取状态寄存器 */
void norflash_write_sr(uint8_t regno,uint8_t sr); /* 写状态寄存器 */
void norflash_erase_chip(void); /* 整片擦除 */
void norflash_erase_sector(uint32_t saddr); /* 扇区擦除 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 读取flash */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写入flash */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/NORFLASH/norflash.h"
/* 要写入到FLASH的字符串数组 */
const uint8_t g_text_buf[] = {"STM32 SPI TEST"};
#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */
int main(void)
{
uint8_t key;
uint16_t i = 0;
uint8_t datatemp[TEXT_SIZE];
uint32_t flashsize;
uint16_t id = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(72); /* 初始化USMART */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
norflash_init(); /* 初始化NORFLASH */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "SPI TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED); /* 显示提示信息 */
id = norflash_read_id(); /* 读取FLASH ID */
while ((id == 0) || (id == 0XFFFF)) /* 检测不到FLASH芯片 */
{
lcd_show_string(30, 130, 200, 16, 16, "FLASH Check Failed!", RED);
delay_ms(500);
lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);
delay_ms(500);
LED0_TOGGLE(); /* LED0闪烁 */
}
lcd_show_string(30, 130, 200, 16, 16, "SPI FLASH Ready!", BLUE);
flashsize = 16 * 1024 * 1024; /* FLASH 大小为16M字节 */
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES) /* KEY1按下,写入 */
{
lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */
lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", BLUE);
sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
norflash_write((uint8_t *)datatemp, flashsize - 100, TEXT_SIZE); /* 从倒数第100个地址处开始,写入SIZE长度的数据 */
lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", BLUE); /* 提示传送完成 */
}
if (key == KEY0_PRES) /* KEY0按下,读取字符串并显示 */
{
lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH... . ", BLUE);
norflash_read(datatemp, flashsize - 100, TEXT_SIZE); /* 从倒数第100个地址处开始,读出SIZE个字节 */
lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); /* 提示传送完成 */
lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE); /* 显示读到的字符串 */
}
i++;
if (i == 20)
{
LED0_TOGGLE(); /* LED0闪烁 */
i = 0;
}
delay_ms(10);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。