引言
在嵌入式系统中,外部存储器如串行FLASH在数据存储和管理方面起到了至关重要的作用。本文将介绍如何在STM32平台上通过软件模拟SPI(Soft SPI)完成对W25Q64串行FLASH的控制,并编写相应的驱动程序。这个过程是移植文件系统FatFs的第一步。
准备工作
硬件准备
- STM32开发板:本文使用野火霸道开发板,芯片是STM32F103ZET6。
- W25Q64串行FLASH:一个外部存储芯片。
- 连接:开发板上已经将W25Q64的引脚和STM32的GPIO引脚连接好。
软件准备
- Keil MDK: 用于编写和调试代码。
引脚连接
下面是对应野火霸道开发板的引脚连接方案,具体连接根据自己使用开发板而定。
W25Q64引脚 | STM32引脚 |
CS | PA4 |
DO(对应主机MISO) | PA6 |
WP | 3.3V |
GND | GND |
DI(对应主机MOSI) | PA7 |
CLK | PA5 |
HOLD | 3.3V |
VCC | 3.3V |
软件模拟SPI的实现
GPIO初始化
为了更好的移植性,这里将使用到的GPIO引脚通过宏进行了定义,这样在后续有移植需求的情况下,只需要更改宏即可。
//CS(NSS)引脚 片选选普通GPIO即可
#define FLASH_SPI_CS_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_CS_PORT GPIOA
#define FLASH_SPI_CS_PIN GPIO_Pin_4
//SCK引脚
#define FLASH_SPI_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_PIN GPIO_Pin_5
//MISO引脚
#define FLASH_SPI_MISO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MISO_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_PIN GPIO_Pin_6
//MOSI引脚
#define FLASH_SPI_MOSI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MOSI_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_PIN GPIO_Pin_7
/* 控制W25Q64的相关GPIO初始化函数 */
void W25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
FLASH_SPI_CS_APBxClock_FUN(FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE);
/* 配置SPI的 CS引脚 */
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);
/* 配置SPI的 SCK引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
/* 配置SPI的 MOSI引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
/* 配置SPI的 MISO引脚*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
/* 停止信号 FLASH: CS引脚高电平*/
SPI_FLASH_CS_HIGH();
}
SPI基本操作
编写通过SPI来进行读写的函数,因为使用的四线SPI是全双工的协议,发送一个字节数据的同时也会接收到一个字节数据,所以这里将读写一个字节的函数写成一个函数。
//通过下面的宏来完成基本的SPI操作
#define SPI_FLASH_CS_LOW() GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define SPI_FLASH_CS_HIGH() GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define SPI_FLASH_SCLK_LOW() GPIO_ResetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_SCLK_HIGH() GPIO_SetBits( FLASH_SPI_SCK_PORT, FLASH_SPI_SCK_PIN )
#define SPI_FLASH_MOSI_LOW() GPIO_ResetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
#define SPI_FLASH_MOSI_HIGH() GPIO_SetBits( FLASH_SPI_MOSI_PORT, FLASH_SPI_MOSI_PIN )
/* SPI读写一个字节数据的函数 */
uint8_t SPI1_ReadWriteByte(u8 dat)
{
u8 i, read_data;
SPI_FLASH_SCLK_HIGH(); //时钟线拉高
for (i = 0; i< 8; i++)
{
read_data <<= 1;
if (0x80 == ((dat << i)&0x80))
{
SPI_FLASH_MOSI_HIGH();
}
else
{
SPI_FLASH_MOSI_LOW();
}
SPI_FLASH_SCLK_LOW(); //拉低时钟线,向总线输出数据
delay_us(1);
SPI_FLASH_SCLK_HIGH(); //拉高时钟线,准备读取数据
delay_us(1);
if (GPIO_ReadInputDataBit(FLASH_SPI_MISO_PORT, FLASH_SPI_MISO_PIN))
{
read_data |= 0x01; //读取到数据位为1
}
}
SPI_FLASH_SCLK_HIGH();
return read_data;
}
W25Q64基础驱动函数
写使能/写禁止函数
查看数据手册,对应指令如下:
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
SPI_FLASH_CS_LOW();
SPI1_ReadWriteByte(W25X_WriteDisable);
SPI_FLASH_CS_HIGH();
}
读写状态寄存器
查看数据手册,对应指令如下: