STM32 软件SPI读写W25Q64

接线图

功能函数

cs 复制代码
//写SS函数
void My_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

//写SCK函数
void My_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

//写MOSI函数
void My_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

//读MISO函数
uint8_t My_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

代码配置

1.开启时钟

复制代码
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIO A族的时钟

2.配置GPIO

引脚配置,将输出引脚配置为推挽输出,输入引脚配置为上拉输入,这里接线图,DO对应从机输出,对应主机PA6就是输入,其他三个引脚配置为推挽输出。

复制代码
GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO结构体变量	

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);

3.配置电平

bash 复制代码
My_W_SS(1);//置高电平,默认不选择从机
My_W_SCK(0);//默认选择低电平,使用SPI模式0

配置SPI时序基本单元

起始条件与终止条件

起始条件:SS从高电平切换到低电平

终止条件:SS从低电平切换到高电平

cs 复制代码
//起始条件
void My_SPI_Start(void)
{
	//置低电平
	My_W_SS(0);
}

//终止条件
void My_SPI_Stop(void)
{
	//置高电平
	My_W_SS(1);
}

交换一个字节

方法一

使用掩码,以此提取数据的每一位

cs 复制代码
//交换一个字节
uint8_t My_SPI_SwapByte(uint8_t ByteSend)
{
	
	uint8_t i, ByteReceive = 0x00;
	
	for(i = 0; i < 8; i++)
	{
		//写MOSI
		My_W_MOSI(ByteSend & (0x80 >> i));
		//SCK上升沿 置高电平
		My_W_SCK(1);
		//读MISO
		if(My_R_MISO() == 1)
		{
			ByteReceive |= (0x80 >> i);
		}
		//SCK产生下降沿 置低电平
		My_W_SCK(0);
	}
	
	return ByteReceive;
	
}

方法二

用移位数据本身来进行操作,好处是效率高,但是ByteSend数据在移位的过程中改变了,for循环执行完,原始传入的参数就没有了

cs 复制代码
//交换一个字节
uint8_t My_SPI_SwapByte2(uint8_t ByteSend)
{
	
	uint8_t i;
	
	for(i = 0; i < 8; i++)
	{
		//写MOSI
		My_W_MOSI(ByteSend & 0x80 );
		ByteSend <<= 1;
		//SCK上升沿 置高电平
		My_W_SCK(1);
		//读MISO
		if(My_R_MISO() == 1)
		{
			ByteSend |= 0x01;
		}
		//SCK产生下降沿 置低电平
		My_W_SCK(0);
	}
	
	return ByteSend;
	
}

SPI配置完成,接下来建立W25Q64的驱动层

W25Q64配置

cs 复制代码
#include "stm32f10x.h"         
#include "MySPI.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

接下来实现业务代码,拼接完整时序

宏定义指令集

根据手册指令表将指令宏定义出来

cs 复制代码
//宏定义指令集
#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

实现获取ID号的时序

*MID输出8位的厂商ID

*DID输出16位的设备ID

cs 复制代码
//获取ID号的时序
void W25Q64_ReadID(uint8_t* MID, uint16_t* DID)
{
	//开始传输
	My_SPI_Start();
	//交换发送一个字节 9F
	My_SPI_SwapByte(W25Q64_JEDEC_ID);
	//给从机一个东西,目的是将对方的数据置换过来
	*MID = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);
	//返回设备ID的高8位
	*DID = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;	//把第一次读取的数据运到DID的高8位去
	//返回设备ID的低8位
	*DID |= My_SPI_SwapByte(W25Q64_DUMMY_BYTE);
	//结束时序
	My_SPI_Stop();
}

写使能

cs 复制代码
//写使能
void W25Q64_WriteEnable(void)
{
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_WRITE_ENABLE);
	My_SPI_Stop();
	
}

读状态寄存器1

判断芯片忙不忙

cs 复制代码
//读状态寄存器1
//判断芯片是否处于忙状态
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	//等待BUSY
	while ((My_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	My_SPI_Stop();
}

页编程函数

根据Flash注意事项

写入操作前必须先写使能,写入操作后等待忙状态,这里的页编程与扇形擦除进行了写入操作,需要注意事项。

cs 复制代码
//页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();//写使能
	
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_PAGE_PROGRAM);
	//指定24位地址
	My_SPI_SwapByte(Address >> 16);
	My_SPI_SwapByte(Address >> 8);
	My_SPI_SwapByte(Address);
	for(i = 0; i < Count; i++)
	{
		My_SPI_SwapByte(DataArray[i]);
	}
	My_SPI_Stop();
	
	W25Q64_WaitBusy();//等待BUSY(事后等待)
}

扇区擦除函数

cs 复制代码
//扇区擦除函数
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();//写使能
	
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	//指定24位地址
	My_SPI_SwapByte(Address >> 16);
	My_SPI_SwapByte(Address >> 8);
	My_SPI_SwapByte(Address);
	My_SPI_Stop();
	W25Q64_WaitBusy();//等待BUSY(事后等待)
	
}

读取数据

cs 复制代码
//读取数据
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray, uint32_t Count)
{
	uint32_t i;
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_READ_DATA);
	//指定24位地址
	My_SPI_SwapByte(Address >> 16);
	My_SPI_SwapByte(Address >> 8);
	My_SPI_SwapByte(Address);
	for(i = 0; i < Count; i++)
	{
		DataArray[i] = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	My_SPI_Stop();
	
}

主函数

cs 复制代码
#include "W25Q64.h"
int main(void)
{
	uint8_t MID;
	uint16_t DID;
	uint8_t ArraryWrite[] = {0x01, 0x02, 0x03, 0x04};
	uint8_t ArraryRead[4];
	OLED_Init();
	W25Q64_Init();
	OLED_ShowString(1, 1, "MID:");
	OLED_ShowString(1, 8, "DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000, ArraryWrite, 4);
	W25Q64_ReadData(0x000000, ArraryRead, 4);
	
	OLED_ShowHexNum(2, 3, ArraryWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArraryWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArraryWrite[2], 2);
	OLED_ShowHexNum(2, 12,ArraryWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArraryRead[0], 2);
	OLED_ShowHexNum(3, 6, ArraryRead[1], 2);
	OLED_ShowHexNum(3, 9, ArraryRead[2], 2);
	OLED_ShowHexNum(3, 12,ArraryRead[3], 2);
	
	while(1)
	{
		
		
		
	}
	
	
}
相关推荐
繁星无法超越2 小时前
详解Windows(九)——系统性能优化
windows·stm32·性能优化
small_wh1te_coder8 小时前
从经典力扣题发掘DFS与记忆化搜索的本质 -从矩阵最长递增路径入手 一步步探究dfs思维优化与编程深度思考
c语言·数据结构·c++·stm32·算法·leetcode·深度优先
WKJay_9 小时前
深入理解 Cortex-M3 特殊寄存器
stm32·单片机·嵌入式硬件
Linux嵌入式木子11 小时前
# 2-STM32F103-复位和时钟控制RCC
stm32·单片机·嵌入式硬件
小智学长 | 嵌入式13 小时前
单片机-STM32部分:13-1、编码器
单片机·嵌入式硬件
暗碳13 小时前
WF24 wifi/蓝牙模块串口与手机蓝牙通信
嵌入式硬件
#金毛15 小时前
一、HAL库的设计理念详解:从架构到实践
stm32·嵌入式硬件·架构
alive90316 小时前
STM32移植LVGL8.3 (保姆级图文教程)
stm32·单片机·嵌入式硬件·stm32f407·lvgl8.3·lvgl移植
Camellia031117 小时前
嵌入式学习--江协51单片机day6
嵌入式硬件·学习·51单片机
外星猪猪17 小时前
嵌入式调试新宠!J-Scope:免费+实时数据可视化,让MCU调试效率飙升!
单片机·嵌入式硬件·信息可视化