当前位置: 首页 > article >正文

STM32 软件SPI读写W25Q64

接线图

 功能函数

//写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.配置电平

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

配置SPI时序基本单元

起始条件与终止条件

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

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

//起始条件
void My_SPI_Start(void)
{
	//置低电平
	My_W_SS(0);
}

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

交换一个字节

方法一

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

//交换一个字节
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循环执行完,原始传入的参数就没有了

//交换一个字节
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配置

#include "stm32f10x.h"         
#include "MySPI.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

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

 宏定义指令集

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

//宏定义指令集
#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

//获取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();
}

写使能

//写使能
void W25Q64_WriteEnable(void)
{
	My_SPI_Start();
	My_SPI_SwapByte(W25Q64_WRITE_ENABLE);
	My_SPI_Stop();
	
}

读状态寄存器1

判断芯片忙不忙

//读状态寄存器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注意事项

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

//页编程
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(事后等待)
}

扇区擦除函数

//扇区擦除函数
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(事后等待)
	
}

读取数据

//读取数据
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();
	
}

主函数

#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)
	{
		
		
		
	}
	
	
}


http://www.kler.cn/a/542090.html

相关文章:

  • 【docker】docker改动镜像并重新编译举例
  • DEEPSEEK与GPT等AI技术在机床数据采集与数字化转型中的应用与影响
  • 如何评估云原生GenAI应用开发中的安全风险(下)
  • 1.【线性代数】——方程组的几何解释
  • 高阶C语言|枚举与联合
  • 【04】RUST特性
  • 论文笔记-CIKM2023-GALORE
  • 攻克AWS认证机器学习工程师(AWS Certified Machine Learning Engineer) - 助理级别认证:我的成功路线图
  • [漏洞篇]目录遍历漏洞详解
  • 活动预告 |【Part 2】Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁
  • 说一下 jvm 有哪些垃圾回收器?
  • 在mac中安装Colima使用docker(替代Docker Desktop)
  • Vue 3 嵌套请求与数据重组:挑战与应对
  • ArcGIS Pro SDK (二十七)自定义许可
  • Go语言的内存分配原理
  • 泛型 什么是泛型 泛型的继承和通配符 二叉树
  • hbase快照同步到目标集群出现ERROR Multiple regions have the same startkey问题分析
  • OpenHarmony应用开发学习路线与资源指南
  • 请解释 JavaScript 中的函数式编程,优缺点是什么?
  • 三步本地部署deepseekr1,支持macOs,ubuntu,Windows
  • 基于STM32单片机智能教室管理系统设计与实现
  • 怎么在win10系统批量生成下面目录示例文件?
  • Softhsm储存安全数据性能整理
  • SQLMesh系列教程-2:SQLMesh入门项目实战(下篇)
  • TCP基础知识
  • 【网络安全】服务器安装Docker及拉取镜像教程