【STM32】硬件SPI读写W25Q64芯片

news2024/11/27 14:33:45

目录

基础知识回顾:

SPI外设简介

SPI框图

主模式全双工连续传输

非连续传输

初始化SPI外设

核心代码 - 交换一个字节

硬件接线图

Code

程序配置过程

MySPI.c

MySPI.h

W25Q64.c

W25Q64.h

W25Q64_Ins.h

main.c


基础知识回顾:

【STM32】SPI通信

【STM32】软件SPI读写W25Q64芯片

学习视频:【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p=39&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933


SPI外设简介

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8位/16位数据帧、高位先行/低位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议(音频)
  • STM32F103C8T6硬件SPI资源:SPI1(APB2)、SPI2(APB1)

SPI框图

简化结构

  • TDR数据整体转入移位寄存器的时刻,置TXE标志位
  • 移位寄存器数据整体转入RDR的时刻,置RXNE标志位

TDR、RDR、TXE、RXNE几个关键词


主模式全双工连续传输


非连续传输

非连续传输的整体步骤:

第一个字节

  1. 等待TXE为1
  2. 写入发送的数据至TDR
  3. 等待RXNE为1
  4. 读取RDR接收的数据
  5. 交换下一个字节,重复上述4步骤!!!

第二个字节

  1. 等待TXE为1
  2. 写入发送的数据至TDR
  3. 等待RXNE为1
  4. 读取RDR接收的数据
  5. 交换下一个字节,重复上述4步骤!!!

第...个字节

……

我们可以将标红的4步骤封装成一个函数,每调用一次,就交换一个字节!妙哉~妙哉~

初始化SPI外设

讲解看注释即可

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,  ENABLE);	//开启SPI1的时钟 	
	
	/*GPIO初始化*/
	//SS = PA4 从机选择引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA4引脚初始化为推挽输出
	
	//SCK = GPIO_Pin_5、MOSI =  GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA5、PA7引脚初始化为复用推挽输出

	//MISO = PA6 上拉输入	
	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);				//将PA6引脚初始化为上拉输入

	/*初始化SPI外设*/
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	//主机
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//双线全双工模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//8位数据帧
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率预分频器,目前时钟频率 = 72MHz/128 = 562.5KHz,如果是SPI2的外设,就是用36M/128
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;	//模式0,CPOL时钟极性,空闲默认低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,选择1Edge就是CPHA=0,第一个边沿采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;	//外设的NSS引脚,一般用不到,所以就选软件模式
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC校验的多项式,我们用不到,就填默认值7
	SPI_Init(SPI1, &SPI_InitStructure);

	//使能SPI外设
	SPI_Cmd(SPI1, ENABLE);

//	/*设置默认电平*/
	MySPI_W_SS(1); 	// SS置高, 默认不选中从机
//	MySPI_W_SCK(0);	// 计划使用模式0, 默认低电平
//	MOSI 没有明确规定,MISO是输入引脚,不用输出电平状态

}

核心代码 - 交换一个字节

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET );		//等待TXE为1
	
	SPI_I2S_SendData(SPI1, ByteSend);	//ByteSend写入到TDR寄存器,之后自动转入移位寄存器,一旦寄存器有数据了,时序波形自动产生
	
	while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET );	//等待RXNE=1,表示收到一个字节,也表示发送时序产生完成
	
	return SPI_I2S_ReceiveData(SPI1);	//读取RDR
}

硬件接线图

硬件接线和上一篇文章相同

SPI相关库函数

void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLyinAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

Code

代码只需要再上一篇文章稍作修改即可

W25Q64的C文件和H文件内容都没有变,只修改了MySPI.c文件

根据上述非连续传输的步骤,编写代码,废话就不讲了,直接看代码注释

程序配置过程

①配置相关引脚的复用功能,使能SPIx时钟
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
②初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
③使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
④SPI传输数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
⑤查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_53762042/article/details/117134887

MySPI.c

#include "MySPI.h"

/*引脚配置层*/
/*硬件SPI*/

/**
  * 函    数:SPI写SS引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void  MySPI_W_SS(uint8_t BitValue)	//CS引脚(SS引脚)PA4,从机选择引脚,还是用软件模拟的
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);	//根据BitValue,设置SS引脚的电平
}


// SPI速度非常快,操作完引脚,就不需要加延时了

/*
	输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
	对主机来说,时钟、主机输出、片选都是输出引脚---推挽输出
	主机输入MISO---输出引脚---选择上拉输入	
	从机(W25Q64)的DO输出,是主机输入---PA6
*/
/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,  ENABLE);	//开启SPI1的时钟 	
	
	/*GPIO初始化*/
	//SS = PA4 从机选择引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA4引脚初始化为推挽输出
	
	//SCK = GPIO_Pin_5、MOSI =  GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA5、PA7引脚初始化为复用推挽输出

	//MISO = PA6 上拉输入	
	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);				//将PA6引脚初始化为上拉输入

	/*初始化SPI外设*/
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	//主机
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//双线全双工模式
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//8位数据帧
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率预分频器,目前时钟频率 = 72MHz/128 = 562.5KHz,如果是SPI2的外设,就是用36M/128
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;	//模式0,CPOL时钟极性,空闲默认低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,选择1Edge就是CPHA=0,第一个边沿采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;	//外设的NSS引脚,一般用不到,所以就选软件模式
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC校验的多项式,我们用不到,就填默认值7
	SPI_Init(SPI1, &SPI_InitStructure);

	//使能SPI外设
	SPI_Cmd(SPI1, ENABLE);

//	/*设置默认电平*/
	MySPI_W_SS(1); 	// SS置高, 默认不选中从机
//	MySPI_W_SCK(0);	// 计划使用模式0, 默认低电平
//	MOSI 没有明确规定,MISO是输入引脚,不用输出电平状态

}


/*协议层*/


/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);	//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);	//拉高SS,终止时序
}



//交换一个字节(读写一个字节),W25Q64系列,支持模式0和模式3
//这里选择模式0
//ByteSend是传进来的参数,通过交换一个字节发送出去,接受
//硬件SPI - 发送同时接收
/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET );		//等待TXE为1
	
	SPI_I2S_SendData(SPI1, ByteSend);	//ByteSend写入到TDR寄存器,之后自动转入移位寄存器,一旦寄存器有数据了,时序波形自动产生
	
	while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET );	//等待RXNE=1,表示收到一个字节,也表示发送时序产生完成
	
	return SPI_I2S_ReceiveData(SPI1);	//读取RDR
}

MySPI.h

#ifndef __MYSPI_H__
#define __MYSPI_H__

#include "stm32f10x.h"                  // Device header

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

W25Q64.c

#include "W25Q64.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

/*读取ID,第一个字节:厂商ID。设备ID:第二个字节:存储器类型;第三个字节:容量*/
/**
  * 函    数:W25Q64读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);			// 0x9F, 读取ID号码指令,这里的返回值没有意义,就不需要了
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); 	// 这次交换,把数据给主机,接收的数据是厂商ID变量*MID,发送的数据任意给,一般给0xFF
												//  这里是在通信,通信是有时序的,不同时间调用相同的函数,意义就是不一样的
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);  	// 设备ID的高八位(第三次交换)
	*DID <<= 8;								   	// 高八位移到左边
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); 	// 设备ID的低八位(用或运算,整合数据)
	MySPI_Stop();
}

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
	MySPI_Start();							//SPI起始
	MySPI_SwapByte(W25Q64_WRITE_ENABLE); 	//交换发送 写使能的指令
	MySPI_Stop();							//SPI终止
}


// 发送指令码05,发完指令码,读取状态寄存器,查看是否是忙状态,最低位BUSY,1是忙,0是不忙
/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void) // 等待busy位为0
{
	uint32_t Timeout;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令
	Timeout = 100000;							//给定超时计数时间
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位
	{
		Timeout --;								//等待时,计数值自减
		if (Timeout == 0)						//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;								//跳出等待,不等了
		}
	}
	MySPI_Stop();								//SPI终止
}
/*注意:
		W25Q64_WaitBusy,事前等待&事后等待
		事后等待只需要再写入操作前调用;
		事前等待在写入操作和读取操作之前都得调用
*/

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray	用于写入数据的数组(指针传递数组)
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();						//写入操作前,必须先写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙,事后等待比较保险
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙,不忙就退出这个函数了
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	
	MySPI_Stop();								//SPI终止
}


W25Q64.h

#ifndef __W25Q64_H__
#define __W25Q64_H__

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"     //指令的头文件

void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

#endif

W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06	//写使能
#define W25Q64_WRITE_DISABLE						0x04	//写失能
#define W25Q64_READ_STATUS_REGISTER_1				0x05	//读状态寄存器1
#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	//读取ID号
#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	//无用数据

#endif

main.c

#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
//uint8_t ArrayWrite[] = {0x11, 0x22, 0x33, 0x44};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
	/*模块初始化*/
	OLED_Init();						//OLED初始化
	W25Q64_Init();						//W25Q64初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号,指针 返回输出参数
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID,显示厂商ID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID,显示设备ID
	
	/*W25Q64功能函数测试*/
	W25Q64_SectorErase(0x000000);					//扇区擦除(写之前先进行扇区擦除操作)
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

烧录代码,测试,OLED显示写入数据和读出数据一致~

还可以参考上一篇文章的测试步骤,一一尝试 

参考测试的文章:

【STM32】软件SPI读写W25Q64芯片

原创笔记,码字不易,欢迎点赞,收藏~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1456410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux CentOS系统安装SQL Server并结合内网穿透实现公网访问本地数据

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

Android---Retrofit实现网络请求:Java 版

简介 在 Android 开发中&#xff0c;网络请求是一个极为关键的部分。Retrofit 作为一个强大的网络请求库&#xff0c;能够简化开发流程&#xff0c;提供高效的网络请求能力。 Retrofit 是一个建立在 OkHttp 基础之上的网络请求库&#xff0c;能够将我们定义的 Java 接口转化为…

汽车网络安全--关于供应商网络安全能力维度的思考

目录 1.关于CSMS的理解 2.OEM如何评审供应商 2.1 质量评审 2.2 网络安全能力评审 3.小结 1.关于CSMS的理解 最近在和朋友们交流汽车网络安全趋势时&#xff0c;讨论最多的是供应商如何向OEM证明其网络安全能力。 这是很重要的一环&#xff0c;因为随着汽车网络安全相关强…

一文解千惑:3D PCB电路板功能分区的关键要素

随着科技的不断发展&#xff0c;3D PCB电路板已成为电子工程领域的新宠。与传统的平面电路板相比&#xff0c;3D PCB电路板具有更多的优势&#xff0c;如更高的集成度、更强的信号传输能力和更小的体积。然而&#xff0c;要充分利用3D PCB电路板的优点&#xff0c;功能分区的关…

【C++】C++11中

C11中 1.lambda表达式2.可变参数模板3.包装器 1.lambda表达式 在前面我们学习过仿函数。仿函数的作用到底是干什么的呢&#xff1f; 它为了抛弃函数指针&#xff01; 主要是因为函数指针太难学了 就比如下面这个&#xff0c;看着也挺难受的。 它的参数是一个函数指针&#x…

【C项目】无头单向不循环链表

简介&#xff1a;本系列博客为C项目系列内容&#xff0c;通过代码来具体实现某个经典简单项目 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…

ai图片放大老照片ai处理ps学习

老照片处理 1.bigjpg&#xff1a;AI人工智能图片放大 体验后评价&#xff1a;快速稳定 2.jpgHD&#xff1a;同bigjpg 另支持老照片上色 付费可用&#xff1a;破损修复&#xff0c;魔法动态照片 3.bigmp4&#xff1a;ai视频无损放大 4.jpgrm&#xff1a;ai擦除 利用2023年最先进…

Java入门及环境变量

文章目录 1.1 Java简介1.2 JDK的下载和安装1.3 第一个程序1.4 常见问题1.5 常用DOS命令1.6 Path环境变量 1.1 Java简介 下面我们正式进入Java的学习&#xff0c;在这里&#xff0c;大家第一个关心的问题&#xff0c;应该就是 Java 是什么&#xff0c;我们一起来看一下&#xf…

VMware Workstation 17安装教程:创建虚拟机

虚拟机软件的管理界面 新建虚拟机向导 设置硬件兼容性 设置系统的安装来源 选择操作系统的版本 未完待续&#xff0c;明天继续更新&#xff0c;如有疑问&#xff0c;点击链接加入群聊【信创技术交流群】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kEjDhISXNgJlM…

CV论文--2024.2.19

1、Self-Play Fine-Tuning of Diffusion Models for Text-to-Image Generation 中文标题&#xff1a;自我对弈微调扩散模型&#xff0c;用于文本到图像生成 简介&#xff1a;在生成人工智能&#xff08;GenAI&#xff09;领域&#xff0c;微调扩散模型仍然是一个未被充分探索的…

App启动优化笔记 1

app大致的启动流程。有Launcher进程,system_server进程,zygote进程,APP进程。 Launcher进程:启动activity来启动应用 system_server进程:(ams是其中的一个binder):发送一个socket消息给Zygote。 zygote进程:收到消息后,fork新的进程,---》app进程启动 APP进程:…

Google发布能自我学习能力的Gemini 1.5

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 这波ai浪潮&#xff0c;进化的越来越强大和实用了&#xff0c;OpenAi刚发布了文生视频大模型Sora。而Google发布能了具有自我学习能力的Gemini 1.5。 Google 的大模型以及 AI 聊天机器人都采用 Gemini 这一名称。前…

唯一客服系统:Golang开发客服系统源码,支持网页,H5,APP,微信小程序公众号等接入,商家有PC端管理和H5,可以配置AI智能回复(搭建部署教程)

本系统采用Golang Gin框架GORMMySQLVueElementUI开发的独立高性能在线客服系统。客服系统访客端支持PC端、移动端、小程序、公众号中接入客服&#xff0c;利用超链接、网页内嵌、二维码、定制对接等方式让网上所有通道都可以快速通过本系统联系到商家。 服务端可编译为二进制程…

微软和OpenAI将检查AI聊天记录,以寻找恶意账户

据国外媒体报道&#xff0c;大型科技公司及其附属的网络安全、人工智能产品很可能会推出类似的安全研究&#xff0c;尽管这会引起用户极度地隐私担忧。大型语言模型被要求提供情报机构信息&#xff0c;并用于帮助修复脚本错误和开发代码以侵入系统&#xff0c;这将很可能会成为…

【JVM篇】什么是运行时数据区

文章目录 &#x1f354;什么是运行时数据区⭐程序计数器⭐栈&#x1f50e;Java虚拟机栈&#x1f388;栈帧的内容 &#x1f50e;本地方法栈 ⭐堆⭐方法区 &#x1f354;什么是运行时数据区 运行时数据区指的是jvm所管理的内存区域&#xff0c;其中分为两大类 线程共享&#xf…

Unity导出Android studio项目遇到的aar无法打包问题

Android Studio 接入现有aar 前因,开发过程中,发现Unity打包出来的android包,带有aar,随着android studio打包的过程中,发现要么提示aar要从网络下载,下载不到,要么提示当前aar不能直接在本地引入(玄学,之前一直不会),会导致损坏。 原因,Android studio版本高,An…

OpenCV中inRange函数

在OpenCV中&#xff0c;inRange函数用于根据颜色范围从图像中提取特定的颜色区域。这个函数检查输入图像中的每个像素&#xff0c;如果像素值位于指定的范围内&#xff0c;则在输出图像&#xff08;或掩码&#xff09;中对应位置的像素被设置为白色&#xff08;或者说是255&…

救命~女儿这样穿也太好看了吧

充满青春活力感的 一件小熊针织学院风开衫 小编墙裂推荐哦早春天气微凉 这件抗起球包芯纱材质的开衫 厚度就刚刚好里面随意搭件T恤来穿 上学还是日常出游穿都很合适

传奇手游白日门【天玺996】win架设服务端+双端+GM授权后台+详细教程

资源下载地址&#xff1a;传奇手游白日门【天玺996】win架设服务端双端GM授权后台详细教程 - 海盗空间

数据结构1.0(基础)

近java的介绍&#xff0c; 文章目录 第一章、数据结构1、数据结构 &#xff1f;2、常用的数据结构数据结构&#xff1f; 逻辑结构and物理结构 第二章、数据结构基本介绍2.1、数组&#xff08;Array&#xff09;2.2、堆栈&#xff08;Stack&#xff09;2.3、队列&#xff08;Que…