暂时只是粗略的写了一下,以后有时间再进行详细补充
代码移植野火stm32库函数教程
--------------------------------------------------------------------------------------------------------------------------------
串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。
通常SPI通过4个引脚与外部器件相连:
- MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
- MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
- SCK:串口时钟,作为主设备的输出,从设备的输入。
- NSS:从设备选择。
1. 时钟信号的相位和极性
SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系。CPOL时钟极性和CPHA时钟相位的组合选择数据捕捉的时钟边沿
CPOL(时钟极性)位:控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效
如果CPOL被清 '0',SCK引脚在空闲状态保持低电平
如果CPOL被置 '1',SCK引脚在空闲状态保持高电平
CPHA(时钟相位)位:控制什么时候进行数据采样和数据锁存
如果CPHA被置 '1',SCK时钟的第二个边沿进行数据位的采样,数据在第二个时钟边沿被锁存
如果CPHA被清 '0',SCK时钟的第一个边沿进行数据位的采样,数据在第一个时钟边沿被锁存
NOTE:
- 在改变 CPOL/CPHA 位之前,必须清除SPE位将SPI禁止
- 主和从必须配置成相同的时序模式
- SCK的空闲状态必须和 SPI_CR1 寄存器指定的极性一致(CPOL为 '1 '时,空闲时应上拉SCK为 高电平;CPOL为 '0' 时,空闲时应下拉SCK为低电平)
- 数据帧格式(8位或16位)由 SPI_CR1 寄存器的DFF位选择,并且决定发送/接收的数据长度
2. 配置 SPI 为主模式
2.1. 配置步骤
- 设置 SCK 波特率:通过 SPI_CR1 寄存器的 BR[2:0] 位来配置
- 配置 CPOL 和 CPHA,定义数据传输和串行时钟的相位关系
- 配置 DFF 位来选择数据帧格式:8 位或 16 位
- 配置 SPI_CR1 寄存器的 LSBFIRST 位定义帧格式
在此配置中 MOSI 引脚是数据输出,MISO 引脚是数据输入
2.2. 数据的发送过程
当数据写入发送缓冲器(SPI_DR 寄存器)时,发送过程开始
在发送第一个数据位时,数据字被并行的传入移位寄存器(通过内部总线),然后串行的移出到 MOSI 引脚上
数据从发送缓冲器传输到移位寄存器时 TXE 标志将被置位,如果设置了 SPI_CR1 寄存器的 TEXIE 位,将产生中断
在试图写发送缓冲器之前,需要确认 TXE 标志为 '1'
2.3. 数据的接收过程
3. 寄存器
3.1. SPI_CR1:SPI 控制寄存器 1
4. 使用
4.1. W25Q64BV
4.1.1. 简介
W25Q64BV 阵列被组织成 32768 个可编程页面,每个页面 256 个字节。一次最多可编程 256 个字节
4.1.2. 引脚
- 片选引脚 Chip Select(/CS)
SPI 的片选引脚可以使能和禁用设备运行,当 CS 引脚为高电平时设备被取消选中并且串行数据输出引脚处于高阻抗。在取消选中的情况下,设备的功耗将处于待机状态水平
当 CS 引脚被拉低,设备将被选中,功耗将增加到活跃水平,指令可以被写入设备,数据可以从设备中读取
上电后,在一个新的指令被接受之前,CS 引脚必须从高到底转变
- 串行数据输入输出引脚(DI, DO and IO0, IO1, IO2, IO3,)
W25Q64BV 支持 Standed SPI,Dual SPI 和 Quad SPI 操作
a. Standed SPI
使用非双向的输入引脚串行的写入指令,地址和数据在时钟输入引脚的上升沿
使用非双向的输出引脚从设备中读取数据或者状态在时钟输入引脚的下降沿
b. Dual SPI 和 Quad SPI
使用双向的 IO 引脚串行的写入指令,地址,数据到设备中在时钟输入引脚的上升沿,从设备中读数据或状态在时钟输入引脚的下降沿
Quad SPI instructions require the non-volatile Quad Enable bit (QE) in Status Register-2 to be set. When QE=1 the /WP pin becomes IO2 and /HOLD pin becomes IO3.
- 写保护引脚 Write Protect(/WP)
写保护引脚可以防止状态寄存器被写入
和状态寄存器的块保护位 (SEC, TB, BP2, BP1, BP0) 以及状态寄存器保护位一同使用
一部分或者整个存储器阵列可以被硬件保护
写保护引脚 /WP 低电平有效
4.1.3. 功能描述‘
SPI 总线支持工作在模式 0 和模式 3,模式 0 和模式 3 最主要的区别就是当主控制器处于待机状态和数据没有被传输到串行闪存时时钟信号的正常状态
对于模式 0:CLK 信号在 /CS 引脚的上升沿和下降沿为低
对于模式 3:CLK 信号在 /CS 引脚的上升沿和下降沿为高
4.1.4. 寄存器
- 控制和状态寄存器
读状态寄存器 1( Read Status Register-1)和状态寄存器 2( Status Register-2)指令可以用于提供闪存存储阵列的可用性状态:设备是否写使能或写禁止,写保护状态和 Quad SPI 设置
写状态寄存器( Write Status Register )指令可以设置设备的写保护特征和 Quad SPI 设置
对于状态寄存器的写入访问由非易失性状态寄存器保护位( SRP0, SRP1 ),写使能指令,和写保护引脚( /WP ,在某些情况下)控制
4.1.4.1. 状态寄存器
- BUSY :是一个只读的位在状态寄存器的第 0 位,当设备正在执行页面程序,扇区擦除,块擦除,整片擦除或者写状态寄存器指令时会被置 1,在这期间设备将会忽略除了读状态寄存器和擦除挂起指令之外的其他指令,当程序,擦除或者写状态寄存器指令完成后,BUSY 位将被清 0 表示设备已经准备好接收后面的指令
- Write Enable Latch(WEL):WEL 是一个只读的位在状态寄存器的第 1 位,将会被置 1 在完成一个写使能指令后,当设备被写禁止时WEL 会被清 0 ,写禁止状态会通电后或者以下的任何一个指令之后:
-
- Write Disable
- Page Program
- Sector Erase
- Block Erase
- Chip Erase
- Write Status Register
- Block Protect Bits( BP2, BP1, BP0 ):Block Protect Bits( BP2, BP1, BP0 )是非易失性的 r/w 位在状态寄存器的第 2,3,4 位,用于提供写保护控制和状态,Block Protect Bits 可以被设置通过写状态寄存器指令
存储阵列的全部,无,一部分可以被保护,不受到程序,擦除指令的影响
Block Protect Bits 的出厂设置为 0,没有阵列受到保护
- Top/Bottom Block Protect( TB ):非易失性 TB 位控制 Block Protect Bits 是从阵列的顶部( TB = 0 )还是底部( TB = 1 )进行保护
出场默认设置为 TB = 0
TB 位可以被设置使用写状态寄存器指令,取决于 SRP0,SRP1 和 WEL 位的状态
- Sector/Block Protect( SEC ):SEC 控制 Block Protect Bits 保护 4KB 扇区( SEC = 1 )或者 64KB 块( SEC = 0 )在阵列的顶部或者底部
默认设置为 SEC = 0
- Status Register Protect( SRP1, SRP0 ):Status Register Protect Bits 是非易失性的 r/w 位在状态寄存器的第 7,8 位,SRP 位控制写保护的方式:
-
- 软件保护:Software Protection
- 硬件保护:Hardware Protection
- 电源锁定:power supply lock-down
- 一次性可编程保护:one time programmable( OTP ) protection
- Quad Enable( QE ):QE 位是一个非易失性的 r/w 位在状态寄存器的第 9 位用于设置是否允许 Quad 操作
当 QE 位为 0 (出厂设置)时,/WP 和/Hold 引脚被使能
当 QE 位为 1 时,Quad IO2,IO3 引脚被使能
NOTE: 在 Standard SPI 或者 Dual SPI 操作下,如果 /WP 或 /Hold 被直接连接到电源供应或接地时,QE 位应该禁止被设置成 1
4.1.5. 指令
W25Q64BV 的指令集由 27 条基本指令构成,这些指令通过 SPI 总线完全控制,指令在 /CS 引脚的下降沿初始化
到达 DI 输入的数据的第一个字节提供指令代码
DI 输入上的数据在时钟的上升沿被采样,MSB first
4.1.5.1. 06h Write Enable
写使能指令这是设置状态寄存器的 WEL 位为 1
WEL 位必须在页面编程,扇区擦除,块擦除,整片擦除以及写状态寄存器指令之前设置
写使能指令是通过拉低 /CS 引脚,发送指令代码 06h 到数据输入引脚在 CLK 时钟的上升沿,然后再把 /CS 引脚拉高
4.1.5.2. 04h Write Disable
写禁止指令是设置状态寄存器的 WEL 位位 0
写禁止指令是通过拉低 /CS 引脚,发送指令代码 04h 到数据输入引脚,然后再把 /CS 引脚拉高
NOTE:WEL 位在上电后以及在写状态寄存器,页编程,扇区擦除以及整片擦除完成之后自动复位
4.1.6. 代码
spi.c
#include "spi.h"
/*
* PC0 CS
* PA5 SPI1_SCK
* PA7 SPI1_MOSI
* PA6 SPI1_MISO
*/
static void W25Q64BV_SPI_Config(void)
{
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStruct);
SPI_Cmd(SPI1, ENABLE);
}
static void W25Q64BV_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// SPI1 CS pin: C.0
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
// SPI1 CLK pin: A.5
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// SPI1 MOSI pin: A.6
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// SPI1 MISO pin: A.7
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStruct);
W25Q64VB_CS_HIGH();
}
void W25Q64BV_Init(void)
{
W25Q64BV_SPI_Config();
W25Q64BV_GPIO_Config();
}
uint8_t SPI1_SendByte(uint8_t data)
{
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
{
//
}
SPI_I2S_SendData(SPI1, data);
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
{
//
}
return SPI_I2S_ReceiveData(SPI1);
}
void SPI1_WaitForWriteEnd(void)
{
uint8_t W25Q64BV_Status = 0;
W25Q64VB_CS_LOW();
SPI1_SendByte(ReadStatusRegister1);
do
{
W25Q64BV_Status = SPI1_SendByte(DummyByte);
}
while((W25Q64BV_Status & BUSY_FLAG) == SET);
W25Q64VB_CS_HIGH();
}
void W25Q64BV_WriteEnable(void)
{
W25Q64VB_CS_LOW();
SPI1_SendByte(WriteEnable);
W25Q64VB_CS_HIGH();
}
void W25Q64BV_SectorErase(uint32_t SectorAddr)
{
W25Q64BV_WriteEnable();
SPI1_WaitForWriteEnd();
W25Q64VB_CS_LOW();
SPI1_SendByte(SectorErase);
SPI1_SendByte((SectorAddr & 0xFF0000) >> 16);
SPI1_SendByte((SectorAddr & 0xFF00) >> 8);
SPI1_SendByte(SectorAddr & 0xFF);
W25Q64VB_CS_HIGH();
SPI1_WaitForWriteEnd();
}
void W25Q64BV_ChipErase(void)
{
W25Q64BV_WriteEnable();
SPI1_WaitForWriteEnd();
W25Q64VB_CS_LOW();
SPI1_SendByte(ChipErase);
W25Q64VB_CS_HIGH();
SPI1_WaitForWriteEnd();
}
void W25Q64BV_PageWrite(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData)
{
W25Q64BV_WriteEnable();
W25Q64VB_CS_LOW();
SPI1_SendByte(PageProgram);
SPI1_SendByte((WriteAddr & 0xFF0000) >> 16);
SPI1_SendByte((WriteAddr & 0xFF00) >> 8);
SPI1_SendByte(WriteAddr & 0xFF);
if( LengthOfData > W25Q64BV_MaxPageSize )
{
return;
}
while( LengthOfData-- )
{
SPI1_SendByte(*data);
data++;
}
W25Q64VB_CS_HIGH();
SPI1_WaitForWriteEnd();
}
uint32_t W25Q64BV_ReadJedceID(void)
{
uint8_t temp1, temp2, temp3;
uint32_t temp;
W25Q64VB_CS_LOW();
SPI1_SendByte(ReadJedecID);
temp1 = SPI1_SendByte(DummyByte);
temp2 = SPI1_SendByte(DummyByte);
temp3 = SPI1_SendByte(DummyByte);
W25Q64VB_CS_HIGH();
temp = (temp1 << 16) | (temp2 << 8) | temp3;
return temp;
}
void W25Q64BV_BufferWrite(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData)
{
uint8_t NumOfPage = 0;
uint8_t NumOfSingle = 0;
uint8_t Addr = 0;
uint8_t count = 0;
uint8_t temp = 0;
Addr = WriteAddr % W25Q64BV_MaxPageSize; // 检查要要写入的地址是否页对齐
NumOfPage = LengthOfData / W25Q64BV_MaxPageSize; // 计算出这次一共要写入多少页
NumOfSingle = LengthOfData % W25Q64BV_MaxPageSize; // 计算出这次剩余不满一页的字节数
count = W25Q64BV_MaxPageSize - WriteAddr; // 计算出刚好可以页对齐的字节数
// 如果 Addr == 0 , 则 WriteAddr 刚好是页对齐的, 就从 WriteAddr 开始写入就可以了
if( Addr == 0 )
{
if( NumOfPage == 0 )
{
// 如果要写入的数据不满一页, 直接写入
W25Q64BV_PageWrite(WriteAddr, data, LengthOfData);
}
else
{
// 如果要写如的地址超过了一页, 就先把满一页的数据写完
while( NumOfPage-- )
{
W25Q64BV_PageWrite(WriteAddr, data, LengthOfData);
WriteAddr += W25Q64BV_MaxPageSize;
data += W25Q64BV_MaxPageSize;
}
// 整数页都写完后, 再把剩下的写入后面一页
W25Q64BV_PageWrite(WriteAddr, data, LengthOfData);
}
}
else
{
if( NumOfPage == 0 )
{
if( NumOfSingle > count )
{
W25Q64BV_PageWrite(WriteAddr, data, count);
temp = NumOfSingle - count;
WriteAddr += count;
data += count;
W25Q64BV_PageWrite(WriteAddr, data, temp);
}
else
{
W25Q64BV_PageWrite(WriteAddr, data, LengthOfData);
}
}
else
{
LengthOfData -= count;
NumOfPage = LengthOfData / W25Q64BV_MaxPageSize;
NumOfSingle = LengthOfData % W25Q64BV_MaxPageSize;
W25Q64BV_PageWrite(WriteAddr, data, count);
WriteAddr += count;
data += count;
while( NumOfPage-- )
{
W25Q64BV_PageWrite(WriteAddr, data, W25Q64BV_MaxPageSize);
WriteAddr += W25Q64BV_MaxPageSize;
data += W25Q64BV_MaxPageSize;
}
if (NumOfSingle != 0)
{
W25Q64BV_PageWrite(WriteAddr, data, NumOfSingle);
}
}
}
}
void W25Q64BV_BufferRead(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData)
{
W25Q64VB_CS_LOW();
SPI1_SendByte(ReadDate);
SPI1_SendByte((WriteAddr & 0xFF0000) >> 16);
SPI1_SendByte((WriteAddr& 0xFF00) >> 8);
SPI1_SendByte(WriteAddr & 0xFF);
while( LengthOfData-- )
{
*data = SPI1_SendByte(DummyByte);
data++;
}
W25Q64VB_CS_HIGH();
}
void W25Q64BV_PowerDown(void)
{
W25Q64VB_CS_LOW();
SPI1_SendByte(PowerDown);
W25Q64VB_CS_HIGH();
}
void W25Q64BV_WakeUp(void)
{
W25Q64VB_CS_LOW();
SPI1_SendByte(WakeUp);
W25Q64VB_CS_HIGH();
}
spi.h
#ifndef _SPI_H
#define _SPI_H
#include "stm32f10x.h"
#define W25Q64VB_CS_LOW() GPIO_ResetBits(GPIOC, GPIO_Pin_0)
#define W25Q64VB_CS_HIGH() GPIO_SetBits(GPIOC, GPIO_Pin_0)
#define BUSY_FLAG 0x01
#define W25Q64BV_MaxPageSize 256
#define WriteEnable 0x06
#define WriteDIsable 0x04
#define ReadStatusRegister1 0x05
#define ReadStatusRegister2 0x35
#define WriteStatusRegister 0x01
#define ReadDate 0x03
#define FastRead 0x0B
#define PageProgram 0x02
#define SectorErase 0x20
#define BlockErase32KB 0x52
#define BlockErase64KB 0xD8
#define ChipErase 0xC7 // or 0x60
#define EraseSuspend 0x75
#define EraseResume 0x74
#define PowerDown 0xB9
#define ReadManufacturerOrDeviceID 0x90
#define ReadUniqueIDNumber 0x4B
#define ReadJedecID 0x9F
#define ContinuousReadModeReset 0xFF // or 0xFFFF
#define DummyByte 0xFF
#define WakeUp 0xAB
void W25Q64BV_Init(void);
uint8_t SPI1_SendByte(uint8_t data);
void W25Q64BV_SectorErase(uint32_t SectorAddr);
void W25Q64BV_ChipErase(void);
void SPI1_WaitForWriteEnd(void);
void W25Q64BV_WriteEnable(void);
void W25Q64BV_PageWrite(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData);
void W25Q64BV_BufferWrite(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData);
void W25Q64BV_BufferRead(uint32_t WriteAddr, uint8_t * data, uint16_t LengthOfData);
uint32_t W25Q64BV_ReadJedceID(void);
void W25Q64BV_PowerDown(void);
void W25Q64BV_WakeUp(void);
#endif
main.c
#include "stm32f10x.h"
#include "Dio.h"
#include "delay.h"
#include "usart.h"
#include "spi.h"
#include "adc.h"
#define BufferLength 400
#define EraseAddr 0
#define ReadAddr 0
#define WriteAddr 10
uint8_t WriteBuffer[BufferLength] = {0};
uint8_t ReadBuffer[BufferLength] = {0};
uint32_t ID;
void W25Q64BV_Test(void);
int main(void)
{
delay_init();
W25Q64BV_Init();
ID = W25Q64BV_ReadJedceID();
delay_ms(200);
W25Q64BV_Test();
while(1)
{
//
}
}
// --------------- Test Function ---------------
void W25Q64BV_Test(void)
{
uint16_t i;
for( i = 0; i < BufferLength; i++ )
{
WriteBuffer[i] = 0x66;
}
W25Q64BV_SectorErase(EraseAddr);
W25Q64BV_BufferWrite(WriteAddr, WriteBuffer, 300);
W25Q64BV_BufferRead(ReadAddr, ReadBuffer, 400);
}