SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、 LCD 等设备与 MCU 间,要求通讯速率较高的场合。
SPI 通讯设备之间的常用连接方式见图1。
图1 常见的 SPI 通讯系统
SPI 通讯使用 3 条总线及片选线, 3 条总线分别为 SCK、 MOSI、 MISO,片选线为CS(NSS)。它们的作用介绍如下:
与 I2C 的类似, SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环
节。
SPI 通讯的通讯时序,见图2。
图2 SPI 通讯时序
这是一个主机的通讯时序。 NSS、 SCK、 MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。 MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。以上通讯流程中包含的各个信号分解如下:
在图2 中的标号①处, NSS 信号线由高变低,是 SPI 通讯的起始信号。 NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号⑥处, NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 MOSI及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时, MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用图2 中的 MSB 先行模式。
观察图中的②③④⑤标号处, MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效, MOSI 及 MISO为下一次表示数据做准备。SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。
图2 中的时序只是 SPI 中的其中一种通讯模式, SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性 CPOL”和“时钟相位 CPHA”的概念。时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿” 被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿” 采样。见图3及图4。
图3 CPHA=0 时的 SPI 通讯模式
图4 CPHA=1 时的 SPI 通讯模式
由 CPOL 及 CPHA 的不同状态, SPI 分成了四种模式,见表1,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。
表1 SPI 的四种模式
SPI 模式 | CPOL | CPHA | 空闲时 SCK 时钟 | 采样时刻 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 奇数边沿 |
1 | 0 | 1 | 低电平 | 偶数边沿 |
2 | 1 | 0 | 高电平 | 奇数边沿 |
3 | 1 | 1 | 高电平 | 偶数边沿 |
STM32的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为fpclk/2 (STM32F10x型号的芯片默fpclk1为72MHz,fpclk2为36MHz),完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位,可设置数据MSB先行或LSB先行。它还支持双线全双工、双线单向以及单线模式。
图5 SPI 架构图
SPI 的所有硬件架构都从图 25-5 中左侧 MOSI、 MISO、 SCK 及 NSS 线展开的。STM32 芯片有多个 SPI 外设,它们的 SPI 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚,关于 GPIO 引脚的复用功能,以规格书为准。
实际应用中,我们一般不使用 STM32 SPI 外设的标准 NSS 信号线,而是更简单地使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
SCK 线的时钟信号,由波特率发生器根据“控制寄存器 CR1”中的 BR[0:2]位控制,该位是对 fpclk时钟的分频因子,对 fpclk的分频结果就是 SCK 引脚的输出时钟频率,计算方法见表2。
表2 BR 位对 fpclk 的分频
BR[0:2] | 分频结果(SCK 频率) |
---|---|
000 | fpclk/2 |
001 | fpclk/4 |
010 | fpclk/8 |
011 | fpclk/16 |
BR[0:2] | 分频结果(SCK 频率) |
---|---|
100 | fpclk/32 |
101 | fpclk/64 |
110 | fpclk/128 |
111 | fpclk/256 |
其中的 fpclk频率是指 SPI 所在的 APB 总线频率, APB1 为 fpclk1, APB2 为 fpckl2。通过配置“控制寄存器 CR”的“CPOL 位”及“CPHA”位可以把 SPI 设置成前面分析的 4 种 SPI 模式。
SPI 的 MOSI 及 MISO 都连接到数据移位寄存器上,数据移位寄存器的数据来源及目标接收、发送缓冲区以及 MISO、 MOSI 线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写 SPI的“数据寄存器 DR”把数据填充到发送 F 缓冲区中,通讯读“数据寄存器 DR”,可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存器 CR1”的“DFF 位”配置成 8 位及 16 位模式;配置“LSBFIRST 位”可选择 MSB 先行还是 LSB 先行。
STM32 使用 SPI 外设通讯时,在通讯的不同阶段它会对“状态寄存器 SR”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。图6 中的是“主模式”流程,即 STM32 作为 SPI 通讯的主机端时的数据收发过程
假如我们使能了 TXE 或 RXNE 中断, TXE 或 RXNE 置 1 时会产生 SPI 中断信号,进入同一个中断服务函数,到 SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用 DMA 方式来收发“数据寄存器 DR”中的数据。
本实验中的 FLASH 芯片(型号: W25Q64)是一种使用 SPI 通讯协议的 NOR FLASH存 储 器 , 它 的CS/CLK/DIO/DO 引 脚 分 别 连 接 到 了 STM32 对 应 的 SPI 引 脚NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS引脚是一个普通的 GPIO,不是 SPI 的专用NSS 引脚,所以程序中我们要使用软件控制的方式。FLASH 芯片中还有 WP 和 HOLD 引脚。
WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。
HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。实验中直接接电源,不使用通讯暂停功能。
实验中W25Q64对应的指令内容如表3所示:
表3 W25Q64对应的指令内容
MANUFACTURER ID | (M7-M0) | |
---|---|---|
Winbond Serial Flash | EFh | |
Device ID | (ID7-ID0) | (ID15-ID0) |
Instruction | ABh, 90h | 9Fh |
W25Q64BV | 16h | 4017h |
INSTRUCTION NAME | BYTE 1 (CODE) | BYTE 2 | BYTE 3 | BYTE 4 | BYTE 5 | BYTE 6 |
---|---|---|---|---|---|---|
Write Enable | 06h | |||||
Write Disable | 04h | |||||
Read Status Register-1 | 05h | (S7–S0) (2) | ||||
Read Status Register-2 | 35h | (S15-S8) (2) | ||||
Write Status Register | 01h | (S7–S0) | (S15-S8) | |||
Page Program | 02h | A23–A16 | A15–A8 | A7–A0 | (D7–D0) | |
Quad Page Program | 32h | A23–A16 | A15–A8 | A7–A0 | (D7–D0, …)(3) | |
Block Erase (64KB) | D8h | A23–A16 | A15–A8 | A7–A0 | ||
Block Erase (32KB) | 52h | A23–A16 | A15–A8 | A7–A0 | ||
Sector Erase (4KB) | 20h | A23–A16 | A15–A8 | A7–A0 | ||
Chip Erase | C7h/60h | |||||
Erase Suspend | 75h | |||||
Erase Resume | 7Ah | |||||
Power-down | B9h | |||||
High Performance Mode | A3h | dummy | dummy | dummy | ||
Continuous Read Mode Reset (4) | FFh | FFh | ||||
Release Power down or HPM / Device ID | ABh | dummy | dummy | dummy | (ID7-ID0) (5) | |
Manufacturer/ Device ID(6) | 90h | dummy | dummy | 00h | (MF7-MF0) | (ID7-ID0) |
Read Unique ID(7) | 4Bh | dummy | dummy | dummy | dummy | (ID63-ID0) |
JEDEC ID | 9Fh | (MF7-MF0) Manufacturer | (ID15-ID8) Memory Type | (ID7-ID0) Capacity | ||
Read Data | 03h | A23-A16 | A15-A8 | A7-A0 | (D7-D0) | |
Fast Read | 0Bh | A23-A16 | A15-A8 | A7-A0 | dummy | (D7-D0) |
Fast Read Dual Output | 3Bh | A23-A16 | A15-A8 | A7-A0 | dummy | (D7-D0, …)(1) |
Fast Read Dual I/O | BBh | A23-A8(2) | A7-A0, M7-M0(2) | (D7-D0, …)(1) | ||
Fast Read Quad Output | 6Bh | A23-A16 | A15-A8 | A7-A0 | dummy | (D7-D0, …)(3) |
Fast Read Quad I/O | EBh | A23-A0, M7-M0(4) | (x,x,x,x, D7-D0, …)(5) | (D7-D0, …)(3) | ||
Octal Word Read Quad I/O(6) | E3h | A23-A0, M7-M0(4) | (D7-D0, …)(3) |
#define USE_Core1 0 //使用芯片判断宏
/*SPI 接口定义-开头****************************/
#define FLASH_SPIx SPI1
#define FLASH_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK RCC_APB2Periph_SPI1
#define FLASH_SPI_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
/*****************SPI-GPIO-宏定义*******************/
//☆CS(NSS)引脚 片选选普通 GPIO 即可--软件控制模式为推挽输出GPIO_Mode_Out_PP☆
#if (USE_Core1 ==1)//作硬件平台判断
#define FLASH_SPI_GPIO_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_CS_PORT GPIOA
#define FLASH_SPI_CS_PIN GPIO_Pin_4
#else
#define FLASH_SPI_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC)
#define FLASH_SPI_CS_PORT GPIOC
#define FLASH_SPI_CS_PIN GPIO_Pin_0
#endif
//SCK 引脚
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_PIN GPIO_Pin_5
//MISO 引脚
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_PIN GPIO_Pin_6
//MOSI 引脚
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_PIN GPIO_Pin_7
/*****************CS引脚配置*******************/
#define FLASH_SPI_CS_HIGH GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
#define FLASH_SPI_CS_LOW GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
/*****************等待超时时间*****************/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/*******************报错信息输出*******************/
#define FLASH_DEBUG_ON 0
#define FLASH_INFO(fmt,arg...) printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...) printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...) do{\
if(FLASH_DEBUG_ON)\
printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
/*******************相关常量定义*******************/
#define DUMMY 0x00
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define sFLASH_ID 0XEF4017
#define Dummy_Byte 0xFF
/********宏封装 FLASH 芯片的常用指令编码*******/
#define W25X_JedecDeviceID 0x9F
#define W25X_SectorErase 0x20
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_WriteData 0x02
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_BlockErase 0xD8
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
static uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
extern uint32_t DeviceID; //主函数传参(接收用)
extern uint32_t FlashID; //主函数传参(接收用)
/**
* @brief SPI 时钟、I/O配置
* @param 无
* @retval 无
*/
static void SPI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 SPI 有关的时钟 */
FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
FLASH_SPI_GPIO_APBxClock_FUN ( FLASH_SPI_GPIO_CLK, ENABLE );
/* SPI_CS(C0)、SPI_SCK、SPI_MISO、SPI_MOSI--模式具体参考中文手册*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用输出
GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用输出
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
//CSS拉高停止通信
FLASH_SPI_CS_HIGH;
}
/**
* @brief SPI 工作模式配置
* @param 无
* @retval 无
*/
static void SPI_Mode_Config(void)
{
SPI_InitTypeDef SPI_InitStructure;
/*结构体配置*/
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //设置为1Edge则无法读取
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CRCPolynomial = 0; //不使用CRC功能,数值随便写
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
/* SPI 初始化 */
SPI_Init(FLASH_SPIx, &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(FLASH_SPIx, ENABLE);
}
/**
* @brief SPI 初始化
* @param 无
* @retval 无
*/
void SPI_FLASH_Init(void)
{
SPI_GPIO_Config();
SPI_Mode_Config();
}
/**
* @brief SPI 发送并接收一个字节
* @param data:发送内容(命令或数据)
* @retval 无
*/
uint8_t SPI_FLASH_Send_Byte(uint8_t data)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
//检查并等待至TX缓冲区为空
while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_TXE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
//程序执行到此处,TX缓冲区已空
SPI_I2S_SendData (FLASH_SPIx,data);
SPITimeout = SPIT_FLAG_TIMEOUT;
//检查并等待至RX缓冲区为非空(SPI发送时也接收,接收缓冲区非空则说明当前data已发送)
while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_RXNE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
//程序执行到此处,说明数据发送完毕,并接收到一个字节的数据
return SPI_I2S_ReceiveData(FLASH_SPIx);
}
/**
* @brief SPI 接收一个字节
* @param 无
* @retval 无
*/
uint8_t SPI_FLASH_Read_Byte(void)
{
return SPI_FLASH_Send_Byte(DUMMY);
}
/**
* @brief SPI 读取FLASH ID号
* @param 无
* @retval 无
*/
uint32_t SPI_Read_ID(void)
{
uint32_t flash_id;
//片选使能
FLASH_SPI_CS_LOW;
//发送读取ID指令
SPI_FLASH_Send_Byte(W25X_JedecDeviceID);
flash_id = SPI_FLASH_Send_Byte(DUMMY);
//MSB决定先接收为高位,因此32位数据左移8位,准备接收下一个字节内容
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
//片选拉高,停止传输通信
FLASH_SPI_CS_HIGH;
//返回32位ID号(ID号实际为24位)
return flash_id;
}
/**
* @brief SPI 读取FLASH Device ID号
* @param 无
* @retval 无
*/
uint32_t SPI_ReadDevice_ID(void)
{
uint32_t flash_id;
//写FLASH使能
SPI_Write_Enable();
//片选使能
FLASH_SPI_CS_LOW;
//发送读取ID指令
SPI_FLASH_Send_Byte(W25X_DeviceID);
flash_id = SPI_FLASH_Send_Byte(DUMMY);
//MSB决定先接收为高位,因此32位数据左移8位,准备接收下一个字节内容
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
//片选拉高,停止传输通信
FLASH_SPI_CS_HIGH;
//返回32位ID号(ID号实际为24位)
return flash_id;
}
/**
* @brief SPI FLASH写入使能(需联系FLASH,一旦通信对象改变则需根据硬件判断改变对应指令)
* @param 无
* @retval 无
*/
void SPI_Write_Enable(void)
{
//片选使能
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(W25X_WriteEnable);
FLASH_SPI_CS_HIGH;
}
/**
* @brief SPI 等待FLASH内部时序操作完成
* @param 无
* @retval 无
*/
void SPI_WaitForWriteEnd(void)
{
uint8_t status_reg = 0;
SPI_Write_Enable();
//片选使能
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(W25X_ReadStatusReg);
//当返回状态位最低位为0时则说明完成写入
do
{
status_reg = SPI_FLASH_Send_Byte(DUMMY);
}
while((status_reg & 0x01) == 1);
FLASH_SPI_CS_HIGH;
}
/**
* @brief SPI 擦除FLASH指定扇区
* @param addr:擦除起始地址
* @retval 无
*/
void SPI_Erase_Sector(uint32_t addr)
{
SPI_Write_Enable();
//片选使能
FLASH_SPI_CS_LOW;
//FLASH擦除指令
SPI_FLASH_Send_Byte(W25X_SectorErase);
//MSB决定发送与接收认定为高位优先
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
//拉高片选,停止传输,并等待写入完成
FLASH_SPI_CS_HIGH;
SPI_WaitForWriteEnd();
}
/**
* @brief SPI 读取FLASH的内容
* @param addr:读取起始地址;*readBuff:存储将读取的数组内容;numByteToWrite:需读取的字节数
* @retval 无
*/
void SPI_Read_Data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead)
{
SPI_Write_Enable();
//片选使能
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(W25X_ReadData);
//发送32位地址
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
//地址发送完成,进行numByteToRead个字节的读取
while(numByteToRead--)
{
*readBuff = SPI_FLASH_Send_Byte(DUMMY);
readBuff++;
}
//拉高片选,停止传输,并等待写入完成
FLASH_SPI_CS_HIGH;
SPI_WaitForWriteEnd();
}
/**
* @brief SPI 向FLASH写入内容
* @param addr:写入起始地址;*writeBuff:存储需写入的数组内容;numByteToWrite:需写入的字节数
* @retval 无
*/
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)
{
SPI_Write_Enable();
//片选使能
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(W25X_WriteData);
//发送写入地址
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
//超出单次可写入页数(字节数)则设置写入字节数为阈值并打印报错
if(numByteToWrite > SPI_FLASH_PerWritePageSize)
{
numByteToWrite = SPI_FLASH_PerWritePageSize;
FLASH_ERROR("SPI_FLASH_PageWrite too large!");
}
while(numByteToWrite--)
{
SPI_FLASH_Send_Byte(*writeBuff);
writeBuff++;
}
//拉高片选,停止传输,并等待写入完成
FLASH_SPI_CS_HIGH;
SPI_WaitForWriteEnd();
}
/**
* @brief Basic management of the timeout situation.
* @param errorCode:错误代码,可以用来定位是哪个环节出错.
* @retval 返回0,表示SPI读取失败.
*/
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* Block communication and all processes */
FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
/**
* @brief SPI 测试函数--获取FLASH DEVICE ID 和FLASH ID,同时向前25个字节写入并读出4kb的内容
* @param *readBuff:存储将读取数据的数组;*writeBuff:存储需写入数据的数组
* @retval 无
*/
void SPI_FLASH_TEST(uint8_t *readBuff, uint8_t *writeBuff)
{
uint16_t i;
/*获取FLASH Device ID*/
DeviceID = SPI_ReadDevice_ID();
SPI_Delay(200);
/*获取SPI FLASH ID*/
FlashID = SPI_Read_ID();
printf("\r\n FlashID is 0x%X,\
Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);
/*前25个字节测试*/
SPI_Erase_Sector(0);//擦除扇区
for(i=0;i<25;i++)
{
writeBuff[i]=i+25;
}
SPI_Write_Data(0,writeBuff,25);
SPI_Read_Data(0,readBuff,4096);
for(i=0;i<4096;i++)
{
printf("0x%x ",readBuff[i]);
if(i%10==0)
printf("\r\n");
}
}
/**
* @brief SPI 延时函数(不精确)
* @param nCount:模糊延时时间,需配合系统时钟进行计算
* @retval 无
*/
void SPI_Delay( uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
/**************BufferWrite****************/
/**
* @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count个数据值,刚好可以对齐到页地址*/
count = SPI_FLASH_PageSize - Addr;
/*计算出要写多少整数页*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod运算求余,计算出剩余不满一页的字节数*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,则WriteAddr 刚好按页对齐 aligned */
if (Addr == 0)
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*先把整数页都写了*/
while (NumOfPage--)
{
SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
}
}
/* 若地址与 SPI_FLASH_PageSize 不对齐 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先写满当前页*/
SPI_Write_Data(WriteAddr, pBuffer, count);
WriteAddr += count;
pBuffer += count;
/*再写剩余的数据*/
SPI_Write_Data(WriteAddr, pBuffer, temp);
}
else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
{
SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
}
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先写完count个数据,为的是让下一次要写的地址对齐 */
SPI_Write_Data(WriteAddr, pBuffer, count);
/* 接下来就重复地址对齐的情况 */
WriteAddr += count;
pBuffer += count;
/*把整数页都写了*/
while (NumOfPage--)
{
SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0)
{
SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
}
}
}
}
uint32_t DeviceID = 0;
uint32_t FlashID = 0;
uint8_t readBuff[4096];
uint8_t writeBuff[4096];
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint32_t id;
LED_GPIO_Config();
LED_BLUE;
/* 配置串口为:115200 8-N-1 */
USART_Config();
printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");
/*8M串行FLASH W25Q64 初始化*/
SPI_FLASH_Init();
/*SPI-FLASH读写测试*/
SPI_FLASH_TEST(readBuff, writeBuff);
}