前言
SPI介绍:https://blog.csdn.net/qq_53922901/article/details/137142038
W25Q64介绍:
https://blog.csdn.net/qq_53922901/article/details/137197048
本文主要介绍使用软件模拟SPI时序来读写W25Q64,其他相关内容可见此专栏
接线
为了方便软件模拟和硬件SPI的切换,可以直接使用硬件SPI的接线,可由下面引脚定义得知
引脚定义图
软件SPI读写W25Q64
代码规划
使用ThisSPI.c来用于书写SPI基本时序单元的内容,然后使用ThisW25Q64.c来基于ThisSPI.c来调用这些SPI基本时序单元来完成读写的功能
代码实现
以下内容都使用同名头文件声明了.c文件中的函数
ThisSPI.c
#include "stm32f10x.h" // Device header
// 封装写入CS位的函数
void ThisSPI_W_CS(uint8_t BitValue){
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
// 封装写入CLK位的函数
void ThisSPI_W_CLK(uint8_t BitValue){
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}
// 封装写入MOSI位的函数
void ThisSPI_W_MOSI(uint8_t BitValue){
GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}
// 封装读取MISO位的函数
uint8_t ThisSPI_R_MISO(void){
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
void ThisSPI_Init(void){
// 初始化引脚配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// 初始化引脚电平,使用SPI状态0
ThisSPI_W_CS(1);
ThisSPI_W_CLK(0);
}
/**
* @brief SPI起始时序单元
* @param 无
* @retval 无
*/
void ThisSPI_Start(void){
ThisSPI_W_CS(0);
}
/**
* @brief SPI结束时序单元
* @param 无
* @retval 无
*/
void ThisSPI_End(void){
ThisSPI_W_CS(1);
}
/**
* @brief SPI交换字节时序单元
* @param Byte 要发送给从机的字节数据
* @retval ByteReceive 从从机接收过来的字节数据
*/
uint8_t ThisSPI_SwapByte(uint8_t Byte){
uint8_t ByteReceive = 0x00,i = 0; // 用于接收交换来的数据
for(i=0;i<8;i++){
// 时钟线下降沿,选择发送的数据
ThisSPI_W_MOSI(Byte & (0x80>>i));
// 时钟线上升沿,接收到从机发送过来的数据
ThisSPI_W_CLK(1);
if(ThisSPI_R_MISO() == 1){
ByteReceive |= (0x80>>i);
}
ThisSPI_W_CLK(0);
}
return ByteReceive;
}
ThisW25Q64.c
#include "stm32f10x.h" // Device header
#include "ThisSPI.h"
// 指令集宏定义
#define JEDEC_ID 0X9F
#define Write_Enable 0X06
#define Read_Status_Register_1 0X05
#define Page_Program 0X02
#define Sector_Erase 0X20
#define Read_Data 0X03
// 初始化
void ThisW25Q64_Init(void){
ThisSPI_Init();
}
// 参考指令集来完成SPI时序单元的拼接
/**
* @brief 读取产品ID
* @param MID 产品ID,DID 设备ID
* @retval 无
*/
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID){
ThisSPI_Start();
ThisSPI_SwapByte(JEDEC_ID);
*MID = ThisSPI_SwapByte(0XFF);
*DID = ThisSPI_SwapByte(0XFF);
*DID <<= 8;
*DID |= ThisSPI_SwapByte(0XFF);
ThisSPI_End();
}
/**
* @brief 写使能
* @param 无
* @retval 无
*/
void W25Q64_WriteEnable(void){
ThisSPI_Start();
ThisSPI_SwapByte(Write_Enable);
ThisSPI_End();
}
/**
* @brief 等待写入完成(通过状态寄存器1的BUSY位)
* @param 无
* @retval 无
*/
void W25Q64_WaitBUSY(void){
uint32_t Timeout = 10000;
ThisSPI_Start();
ThisSPI_SwapByte(Read_Status_Register_1);
while((ThisSPI_SwapByte(0XFF) & 0x01) == 0x01){
Timeout--;
if(Timeout == 0) break;
}
ThisSPI_End();
}
/**
* @brief 页写入
* @param Address,页地址
* @param DataArr,写入的数据
* @param Count,一次写入的大小
* @retval 无
*/
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArr,uint16_t Count){
uint8_t i;
W25Q64_WriteEnable();
ThisSPI_Start();
ThisSPI_SwapByte(Page_Program);
ThisSPI_SwapByte(Address>>16);
ThisSPI_SwapByte(Address>>8);
ThisSPI_SwapByte(Address);
for(i=0;i<Count;i++){
ThisSPI_SwapByte(DataArr[i]);
}
ThisSPI_End();
W25Q64_WaitBUSY();
}
/**
* @brief 扇区擦除
* @param Address,页地址
* @retval 无
*/
void W25Q64_SectorErase(uint32_t Address){
W25Q64_WriteEnable();
ThisSPI_Start();
ThisSPI_SwapByte(Sector_Erase);
ThisSPI_SwapByte(Address>>16);
ThisSPI_SwapByte(Address>>8);
ThisSPI_SwapByte(Address);
ThisSPI_End();
W25Q64_WaitBUSY();
}
/**
* @brief 读取页内数据
* @param Address,页地址
* @param DataArr,用于存取读取的数据
* @param Count,读取数据的大小
* @retval 无
*/
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArr,uint32_t Count){
uint32_t i;
ThisSPI_Start();
ThisSPI_SwapByte(Read_Data);
ThisSPI_SwapByte(Address>>16);
ThisSPI_SwapByte(Address>>8);
ThisSPI_SwapByte(Address);
for(i=0;i<Count;i++){
DataArr[i] = ThisSPI_SwapByte(0XFF);
}
ThisSPI_End();
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "ThisW25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t ArrWrite[] = {0x11,0x92,0x73,0x24};
uint8_t ArrRead[4];
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"MID:");
OLED_ShowString(1,8,"DID:");
ThisW25Q64_Init();
W25Q64_ReadID(&MID,&DID);
OLED_ShowHexNum(1,5,MID,2);
OLED_ShowHexNum(1,12,DID,4);
OLED_ShowString(2,1,"W:");
OLED_ShowString(3,1,"R:");
// 对页的操作一般都以此页页首地址开始,写操作不能跨页,读操作可以跨页
W25Q64_SectorErase(0X000000);
W25Q64_PageProgram(0X000000,ArrWrite,4);
W25Q64_ReadData(0X000000,ArrRead,4);
OLED_ShowHexNum(2,3,ArrWrite[0],2);
OLED_ShowHexNum(2,5,ArrWrite[1],2);
OLED_ShowHexNum(2,7,ArrWrite[2],2);
OLED_ShowHexNum(2,9,ArrWrite[3],2);
OLED_ShowHexNum(3,3,ArrRead[0],2);
OLED_ShowHexNum(3,5,ArrRead[1],2);
OLED_ShowHexNum(3,7,ArrRead[2],2);
OLED_ShowHexNum(3,9,ArrRead[3],2);
while (1)
{
}
}