14. STM32F103C8T6 software SPI read and write W25Q64

Packing 4 communication pins, it is convenient to transplant to other microcontrollers to add delays. The SPI here is fast, and there is no need to add delays after operating the pins.

void MySPI_W_SS(uint8_t bitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)bitValue);
}

void MySPI_W_SCK(uint8_t bitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)bitValue);
}

void MySPI_W_MOSI(uint8_t bitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)bitValue);
}

uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

1. Configure GPIO and timing puzzle of software SPI

1. Software SPI initialization

void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
\t
GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, & amp;GPIO_InitStructure1);
\t
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, & amp;GPIO_InitStructure1);
\t
//After initialization is completed, SS is set to high level, and the slave is selected when it is low level.
MySPI_W_SS(1);
\t
//Use SPI mode 0, SCK defaults to low level
MySPI_W_SCK(0);
}

2. Start signal

void MySPI_Start(void)
{
MySPI_W_SS(0);
}

3. Stop signal

void MySPI_Stop(void)
{
MySPI_W_SS(1);
}

4. Exchange bytes

//Writing method 1:
//SPI exchanges one byte: W25Q64 chip supports mode 0 and mode 3. Mode 0 is used here.
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
uint8_t i, byteReceive = 0x00;
\t
for(i = 0; i < 8; i + + )
{
//As soon as it comes in, there is a falling edge, and the host moves out one bit of data
MySPI_W_MOSI(byteSend & amp; (0x80 >> i));
\t\t
//SCK generates a rising edge, and the slave automatically reads the MOSI data.
MySPI_W_SCK(1);
\t\t
//The host needs to read the data from the slave to MISO
if(MySPI_R_MISO() == 1) //The initial value of byteReceive here is 0x00, so just read 1
{
byteReceive |= (0x80 >> i);
}
\t\t
//SCK generates falling edge
MySPI_W_SCK(0);
}
\t
return byteReceive;
}

//Writing 2:
SPI exchanges one byte: W25Q64 chip supports mode 0 and mode 3. Mode 3 is used here.
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
uint8_t i;
\t
for(i = 0; i < 8; i + + )
{
//As soon as it comes in, there is a falling edge, and the host moves out one bit of data
MySPI_W_MOSI(byteSend & 0x80);
\t\t
//Move byteSend one bit to the left to store the data moved in from the slave.
byteSend <<= 1;
\t\t
//SCK generates a rising edge, and the slave automatically reads the MOSI data.
MySPI_W_SCK(1);
\t\t
//The host needs to put the slave in MISO and read one bit of data to the lowest bit of byteSend
if(MySPI_R_MISO() == 1)
{
byteSend |= 0x01;
}
\t\t
//SCK generates falling edge
MySPI_W_SCK(0);
}
\t
return byteSend;
}

2. Read and write W25Q64

Note: Before the write operation, write enable must be performed first, so add write enable before sector erase and page programming operations.
The timing after writing the enable will be automatically disabled, and there is no need to add a disable

After the write operation is completed, the chip enters the busy state, so the busy function is called after each write operation sequence.

Waiting in the busy state is divided into pre-waiting and post-waiting.
Wait afterwards: After each write, wait for BUSY to clear to 0 before exiting the function. This is the safest way.

Waiting in advance: Before each operation, add the wait busy function at the front of the function. This is more efficient. After the writing is completed, the program can execute other codes and use the time of executing other codes to consume the waiting time.

Post-waiting only needs to be called after the write operation, while pre-waiting needs to wait before both the write operation and the read operation, because the read operation is not allowed when it is busy.

W25Q64 macro definition command

#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3

#define W25Q64_DUMMY_BYTE 0xFF //When receiving, exchange past useless data

1. W25Q64 initialization

void W25Q64_Init(void)
{
MySPI_Init();
}

2. Read ID

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
//Send instructions, there is no need to receive the return value of the function, the return value is meaningless
MySPI_SwapByte(W25Q64_JEDEC_ID);
//Receive manufacturer ID. To receive data from the slave, you need to use bytes to exchange
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
//Receive the high eight digits of the device ID
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
//Receive the lower eight bits of the device ID
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
\t
MySPI_Stop();
}

3. Write enable

void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}

4. Waiting for busy

//Wait for the BUSY bit to be 0/Read status register 1: Determine whether the chip is busy. BUSY is at the lowest bit.
void W25Q64_WaitBusy(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & amp; 0x01) == 0x01);
MySPI_Stop();
}

5. Page programming

//Page programming 24-bit address, because there is no 24-bit data type, 32-bit is used here. A maximum of 256 bytes can be written at a time. If more are written, the first byte will be overwritten.
void W25Q64_PageProgram(uint32_t address, uint8_t *dataArray, uint16_t count)
{
//write enable
W25Q64_WriteEnable();
\t
uint16_t i;
MySPI_Start();
//command
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
//Address: high bit sent first
MySPI_SwapByte(address >> 16);
MySPI_SwapByte(address >> 8);
MySPI_SwapByte(address);
\t//data
for(i = 0; i < count; i + + )
{
MySPI_SwapByte(dataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}

6. Sector erase

void W25Q64_SectorErase(uint32_t address)
{
//write enable
W25Q64_WriteEnable();
\t
MySPI_Start();
//command
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
\t//address
MySPI_SwapByte(address >> 16);
MySPI_SwapByte(address >> 8);
MySPI_SwapByte(address);
MySPI_Stop();
W25Q64_WaitBusy();
}

7. Read data

//Read data. The count of read data can be very large.
void W25Q64_ReadData(uint32_t address, uint8_t *dataArray, uint32_t count)
{
uint32_t i;
MySPI_Start();
//command
MySPI_SwapByte(W25Q64_READ_DATA);
//Address: high bit sent first
MySPI_SwapByte(address >> 16);
MySPI_SwapByte(address >> 8);
MySPI_SwapByte(address);
\t
//After each call to swap read, the memory chip's internal address pointer automatically increments.
for(i = 0; i < count; i + + )
{
dataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
\t
MySPI_Stop();
}