2.37 软件SPI读写W25Q64

W25Q64接线图

PS:因为我的W25Q64模块的引脚虚焊了,所以本节的代码,我使用的是STM32F407的开发板所实现的,在该开发板上面,使用的是W25Q128模块,和W25Q64大差不差。

SPI通信层

#include "stm32f4xx.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_14, (BitAction)(BitValue ? Bit_SET : Bit_RESET));
}

void MySPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_3, (BitAction)(BitValue ? Bit_SET : Bit_RESET));
}

void MySPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_5, (BitAction)(BitValue ? Bit_SET : Bit_RESET));
}

uint8_t MySPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4);
}

void MySPI_Init(void)
{
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStruct;

    // 配置 SCK, MOSI, CS 引脚为输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_14;  // SCK, MOSI, CS
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;             // 输出模式
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;            // 推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;         // 速度50MHz
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;          // 无上下拉
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 配置 MISO 引脚为输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;                 // MISO
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;              // 输入模式
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;              // 上拉
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 初始化 SPI 引脚状态
    MySPI_W_SS(1);    // SS 拉高,未选中
    MySPI_W_SCK(0);   // SCK 初始为低电平
}

void MySPI_Start(void)
{
    MySPI_W_SS(0);   // 将 CS 拉低,选中从设备
}

void MySPI_Stop(void)
{
    MySPI_W_SS(1);   // 将 CS 拉高,释放从设备
}

void Delay(void)
{
    volatile uint32_t i;
    for (i = 0; i < 50; i++);  // 简单延时,具体值可根据需要调整
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00;

    for (i = 0; i < 8; i++)
    {
        // 发送位(先发送高位)
        MySPI_W_MOSI((ByteSend & 0x80) ? 1 : 0);
        ByteSend <<= 1;

        // 拉高时钟
        MySPI_W_SCK(1);
        Delay();

        // 接收位
        ByteReceive <<= 1;
        if (MySPI_R_MISO())
        {
            ByteReceive |= 0x01;
        }

        // 拉低时钟
        MySPI_W_SCK(0);
        Delay();
    }
    return ByteReceive;
}

 

硬件驱动层

可以先做一个ReadID函数来读取芯片的ID,这样做是为了测试通信是否成功。

#include "stm32f4xx.h"                  // Device header
#include "MySPI.h"

void W25Q128_Init(void)
{
	MySPI_Init();
}

//读取ID号时序:起始-随后连续交换3个字节-停止。
//第一个字节是厂商ID,后两个字节是设备ID,其中设备ID的高8位是存储器类型、低8位表示容量。
void W25Q128_ReadID(uint8_t *MID,uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(0x9F);
	*MID = MySPI_SwapByte(0xFF);
	*DID = MySPI_SwapByte(0XFF);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(0xFF);
	MySPI_Stop();
}

 

//获取Busy位,查询芯片是否繁忙。
void W25Q128_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q128_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	//如果Busy位为1,就会进入循环,再次读出一次状态寄存器
	while((MySPI_SwapByte(W25Q128_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

//页编程
void W25Q128_Pageprogram(uint32_t Address,uint8_t *DataArray,uint16_t Count)
{
	//写使能
	W25Q128_WriteEnable();
	
	uint16_t i;
	MySPI_Start();
	//发送地址
	MySPI_SwapByte(W25Q128_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	//发送数据到芯片中
	for(i=0 ; i<Count ;i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	
	MySPI_Stop();
	//等待写入,写入成功后,Busy位置0
	W25Q128_WaitBusy();

}

//擦除所在地址扇区(4K)
void W25Q128_SectorErase(uint32_t Address)
{
	//写使能
	W25Q128_WriteEnable();
	
	MySPI_Start();
	//发送地址
	MySPI_SwapByte(W25Q128_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	
	MySPI_Stop();
	
	//等待写入,写入成功后,Busy位置0
	W25Q128_WaitBusy();
}

//读取数据
void W25Q128_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	//发送地址
	MySPI_SwapByte(W25Q128_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	
	//接收数据到数组中
	for(i=0;i<Count;i++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q128_DUMMY_BYTE);
	}
	MySPI_Stop();
}

 

W25Q128指令

W25Q128_WRITE_ENABLE						0x06
W25Q128_WRITE_DISABLE						0x04
W25Q128_READ_STATUS_REGISTER_1				0x05
W25Q128_READ_STATUS_REGISTER_2				0x35
W25Q128_WRITE_STATUS_REGISTER				0x01
W25Q128_PAGE_PROGRAM						0x02
W25Q128_QUAD_PAGE_PROGRAM					0x32
	
W25Q128_BLOCK_ERASE_64KB					0xD8
W25Q128_BLOCK_ERASE_32KB					0x52
W25Q128_SECTOR_ERASE_4KB					0x20
	
W25Q128_CHIP_ERASE							0xC7
W25Q128_ERASE_SUSPEND						0x75
W25Q128_ERASE_RESUME						0x7A
	
W25Q128_POWER_DOWN							0xB9
	
W25Q128_HIGH_PERFORMANCE_MODE				0xA3
W25Q128_CONTINUOUS_READ_MODE_RESET			0xFF
	
W25Q128_RELEASE_POWER_DOWN_HPM_DEVICE_ID	0xAB
W25Q128_MANUFACTURER_DEVICE_ID				0x90
W25Q128_READ_UNIQUE_ID						0x4B
W25Q128_JEDEC_ID							0x9F
W25Q128_READ_DATA							0x03
W25Q128_FAST_READ							0x0B
W25Q128_FAST_READ_DUAL_OUTPUT				0x3B
W25Q128_FAST_READ_DUAL_IO					0xBB
W25Q128_FAST_READ_QUAD_OUTPUT				0x6B
W25Q128_FAST_READ_QUAD_IO					0xEB
W25Q128_OCTAL_WORD_READ_QUAD_IO				0xE3
	
W25Q128_DUMMY_BYTE							0xFF

 

 

写代码时的注意事项:

一定要记住,写入数据(擦除实际上也是写入)的时候,要记得等待写入完成Busy位,要加一个等待Busy。