STM32-SPI

目录

SPI通信

硬件电路

SPI通信的原理

SPI时序

起始条件

终止条件

交换一个字节(模式0)

交换一个字节(模式1)

指定地址读数据

W25Q64简介

硬件电路

W25Q64框图

Flash操作注意事项

写入操作时

读取操作时

一、软件SPI

程序

注意事项

二、硬件SPI

SPI外设

SPI外设的基本结构图

主模式全双工连续传输

非连续传输

程序

注意事项


SPI通信

  • SPI是由Motorola公司开发的一种通用数据总线;
  • 有四根通信总线:SCL(Serial Clock)、MOSI(Master Output Slove Input)、MISO(Master Input Slave Input)、SS(Slave Slecet);
  • 同步、全双工;
  • 支持总线挂载多个设备(一主多从)

硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连接在一起;
  • 主机另外引出多条SS控制线,分别接到各个从机的SS引脚;
  • 输出引脚配置为推挽输出模式,输入引脚配置为浮空或上拉输入模式

SPI通信的原理

SPI通信的原理是主机和从机之间交换数据。

如上图所示,主机和从机都有一个移位寄存器,例如主机发送数据给从机:当一个时序到来时,主机将一个bit位数据移出至MOSI总线上,从机将数据移至MISO总线上,当下一个时序到来时,主机将MISO上的数据移入移位寄存器中,从机将MOSI上的数据移入移位寄存器中,如此重复八次,完成一个字节数据的传输。(SPI为高位先行)

SPI时序

起始条件

SPI起始条件为:SS从高电平转换为低电平。

终止条件

SPI终止条件为:SS从低电平转换为高电平。

交换一个字节(模式0)

  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=0:SCK的第一个边沿移入数据,第二个边沿移出数据;
  • 此模式一般在SS拉低电平时就会将第一个数据移出

交换一个字节(模式1)

  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=1:SCK的第一个边沿移出数据,第二个边沿移入数据;

PS:模式3和模式3都是在这两个模式下改变而来的,按照(CPOL CPHA)组合起来的二进制数的大小来决定时模式几的。

指定地址读数据

SPI通信时序的组合:先发送要执行的指令+指定的地址(一般不只8位,由要通信的从机芯片决定)+要发送的数据(如果是读取就不用写这部分)

上图是在指定地址读的时序图。

W25Q64简介

W25QXX系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景。(存储容量与具体型号相关,均为24为地址)

硬件电路

W25Q64框图

W25Q64将存储区域分为了128个不同的存储块(0xXXxxxx),再将不同的存储块分为了16个不同的存储扇区(0xXXXxxx),大写为不变部分。

Flash操作注意事项

写入操作时

  1. 写入操作前,必须先进行写使能;
  2. 每个数据位只能由1变成0,不能由0变成1;
  3. 写入数据前必须先擦除,擦除后,所有数据位变为1;
  4. 擦除必须按照最小擦除单元进行擦除;
  5. 连续写入多个字节时,最多写入一页数据,超过页尾位置的数据,会回到页首覆盖写入;
  6. 写入操作结束后,芯片进入忙状态,不响应新的读写操作。

读取操作时

  • 直接调用读取操作,无需使能,无需额外操作,没有页限制,读取操作结束后也不会进入忙状态,但是不能在忙状态读取数据。

一、软件SPI

本实验用PA4、PA5、PA6、PA7分别连接SS、CLK、MISO、MOSI。

程序

先对SPI模块进行编写,MySPI.c文件:

复制代码
#include "stm32f10x.h"                  // Device header

//函数:将对时钟引脚电平的改变进行封装
void MySPI_CLK (uint8_t BitVal)
{
	GPIO_WriteBit (GPIOA ,GPIO_Pin_5 ,(BitAction) BitVal);
}

//函数:将对发送数据过程进行封装
void MySPI_MOSI (uint8_t BitVal)
{
	GPIO_WriteBit (GPIOA ,GPIO_Pin_7 ,(BitAction) BitVal);
}

//函数:将对接收内容过程进行封装
uint8_t MySPI_MISO (void)
{
	return GPIO_ReadInputDataBit (GPIOA ,GPIO_Pin_6);
}

//函数:将对从机选择过程进行封装
void MySPI_SS (uint8_t BitVal)
{
	GPIO_WriteBit (GPIOA ,GPIO_Pin_4 ,(BitAction) BitVal);
}

//函数:对SPI的初始化函数
void MySPI_Init(void)
{
	//打开APB2总线上GPIOA的时钟
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA ,ENABLE);
	
	//对PA4、PA5、PA7进行初始化配置,这里需要配置为推挽输出模式
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 ;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init (GPIOA ,&GPIO_InitStruct );
	
	//对PA6进行初始化配置,这里需要配置为上拉输入模式
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_6 ;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init (GPIOA ,&GPIO_InitStruct );
	
	//对SPI时序的初始状态进行设置,默认为时钟低电平、从机未选择状态
	MySPI_CLK (0);
	MySPI_SS(1);
}

//函数:初始状态,对从机进行选择
void MySPI_Start(void)
{
	MySPI_SS(0);
}

//函数:结束状态,对从机选择进行取消
void MySPI_Stop(void)
{
	MySPI_SS(1);
}

//函数;主机和从从机交换一个字节数据
//函数参数:要发送给从机的数据
//返回值:主机接收到的数据
uint8_t MySPI_Swap(uint8_t send_data)
{
	//创建变量用于存储主机接收到的数据
	uint8_t receive_data = 0x00;
	//创建变量用于存储for循环次数
	uint8_t i;
	//循环语句:用于循环交换一个字节数据
	for(i=0;i<8;i++)
	{
		//向MOSI总线写入一位数据(发送一位数据)
		MySPI_MOSI(send_data & (0x80>>i));
		//将时钟拉高
		MySPI_CLK (1);
		//接收一位数据(用判断+位处理的方式)
		if(MySPI_MISO ()){receive_data |= (0x80>>i);}
		//接收完一位数据后,将时钟拉低
		MySPI_CLK (0);
	}
	//将接收到的一个字节数据返回
	return receive_data;
}

这里用软件模拟的SPI是模式0,空闲状态时钟为低电平,第一个边沿移入数据,第二个边沿移出数据。

MySPI.h文件:

复制代码
#ifndef _MYSPI_H
#define _MYSPI_H
void MySPI_CLK (uint8_t BitVal);
void MySPI_MOSI (uint8_t BitVal);
uint8_t MySPI_MISO (void);
void MySPI_SS (uint8_t BitVal);
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_Swap(uint8_t send_data);


#endif

再对W25Q64模块进行编写,W25Q64.c文件:

复制代码
#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

//对W25Q64进行初始化
void W25Q64_Init(void)
{
	MySPI_Init ();
}

//函数:对W25Q64的ID号进行读取,可以使用这个函数来测试MySPI模块时序是否正确
void Read_ID(uint8_t *MF,uint16_t *ID)
{
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送读取ID指令
	MySPI_Swap(W25Q64_JEDEC_ID);
	//接收MID
	*MF = MySPI_Swap(W25Q64_DUMMY_BYTE);
	//接收DID高8位
	*ID =  MySPI_Swap(W25Q64_DUMMY_BYTE);
	//将接收到的高8位移位至高位
	*ID <<= 8;
	//接收DID的低8位
	*ID |=  MySPI_Swap(W25Q64_DUMMY_BYTE); 
	//对取消对从机的选择
	MySPI_Stop ();
}

//函数:对W25Q64的写使能函数
void Write_Enable(void)
{
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送写使能指令
	MySPI_Swap (W25Q64_WRITE_ENABLE);
	//取消对从机的选择
	MySPI_Stop ();
}

//函数:对Flah进行写操作后,会进入忙状态,该状态下不允许有读写操作,因此需要
//封装一个函数来等待忙状态
void Wait_Busy(void)
{
	//创建一个变量用于存储等待忙状态的时间
	uint32_t Timeout = 100000;
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送读取标志位寄存器指令
	MySPI_Swap (W25Q64_READ_STATUS_REGISTER_1);
	//用while循环去判断忙状态,等待忙状态过去(该状态寄存器最低位为1时是忙状态)
	while((MySPI_Swap (W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		//为了避免程序进行死循环,定义一个合适的等待时间,到时间跳出等待状态
		Timeout --;
		if(Timeout == 0 )
		{
			break ;
		}
	}
	//取消对从机的选择
	MySPI_Stop ();
}

//函数:对指定地址页面进行内容写入
//函数参数:指定地址、要写入的内容、要写入内容的个数
void Page_Program(uint32_t address,uint8_t *DataArray,uint16_t count)
{
	uint8_t i;
	//先进行W25Q64的写使能
	Write_Enable ();
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送写页面指令
	MySPI_Swap (W25Q64_PAGE_PROGRAM);
	//紧接着发送指定24位地址
	//先发送高8位地址(将24位地址向右移动16位,刚好取到高8位)
	MySPI_Swap (address >> 16);
	//再发送中8位地址(将24位地址向右移动8位,刚好取到中8位)
	MySPI_Swap (address >> 8);
	//最后发送低8位地址,直接取到低8位
	MySPI_Swap (address);
	//用for循环对要发送的数据依次进行发送
	for(i=0;i<count;i++)
	{
		//依次啊发送一个字节数据
		MySPI_Swap (DataArray [i]);
	}
	//取消对从机的选择
	MySPI_Stop ();
	//写操作后需要等待忙
	Wait_Busy ();
}

//函数:擦除函数,必须以最小擦除单元进行擦除
//函数参数:要擦除扇区的首地址
void Sector_Erase(uint32_t address)
{
	//向W25Q64进行写使能
	Write_Enable ();
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送擦除指令
	MySPI_Swap (W25Q64_SECTOR_ERASE_4KB);
	//对要擦除区域首地址进行发送
	MySPI_Swap (address >> 16);
	MySPI_Swap (address >> 8);
	MySPI_Swap (address);
	//取消对从机的选择
	MySPI_Stop ();
	//写操作后需要等待忙
	Wait_Busy ();
}

//函数:读取从机中的数据
//函数参数:要读取数据的首地址,用于存储读取到的数据的数组,读取的数据个数
void Read_Data(uint32_t address,uint8_t *DataArray,uint16_t count)
{
	uint16_t i;
	//对从机进行选择
	MySPI_Start ();
	//向W25Q64发送读取指令
	MySPI_Swap (W25Q64_READ_DATA);
	//发送要读取数据的24位的首地址
	MySPI_Swap (address >> 16);
	MySPI_Swap (address >> 8);
	MySPI_Swap (address);
	//用for循环依次读取从机数据
	for(i=0;i<count;i++)
	{
		DataArray[i] = MySPI_Swap (W25Q64_DUMMY_BYTE);
	}
	//取消对从机的选择
	MySPI_Stop ();
}

W25Q64.h文件:

复制代码
#ifndef _W25Q64_H
#define _W25Q64_H
void W25Q64_Init(void);
void Read_ID(uint8_t *MF,uint16_t *ID);
void Write_Enable(void);
void Wait_Busy(void);
void Page_Program(uint32_t address,uint8_t *DataArray,uint16_t count);
void Sector_Erase(uint32_t address);
void Read_Data(uint32_t address,uint8_t *DataArray,uint16_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
#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

#endif

上面这个文件是对W25Q64的相关指令进行宏定义,便于使用。

最后在主函数中对各模块函数进行调用实现对W25Q64Flash的读写操作,main.c文件:

复制代码
#include "stm32f10x.h"    // Device header
#include "OLED.h"
#include "W25Q64.h"

//创建变量用于存储MID和DID
uint8_t MID;
uint16_t DID;
//创建一个数组用于存储要写入到W25Q64Flah中的数据
uint8_t Data_W[3]={0x09,0x77,0x88};
//创建一个数组用于存储从W25Q64Flah中读取到的数据
uint8_t Data_R[3];
int main(void)
{	//对各个模块进行初始化
	OLED_Init();
	W25Q64_Init ();
	
	//显示提示字符串
	OLED_ShowString (1,1,"MID:   DID:");
	//读取ID
	Read_ID(&MID,&DID);
	//显示MID和DID
	OLED_ShowHexNum (1,5,MID,2);
	OLED_ShowHexNum (1,12,DID,4);
	//显示提示字符串
	OLED_ShowString (2,1,"Data_W:");
	OLED_ShowString (3,1,"Data_R:");
	//在进行写入操作前先进行扇区擦除
	Sector_Erase (0x000000);
	//进行页面写入数据
	Page_Program (0x000000,Data_W,3);
	//显示写入到W25Q64的数据
	OLED_ShowHexNum (2,8,Data_W[0],2);
	OLED_ShowHexNum (2,11,Data_W[1],2);
	OLED_ShowHexNum (2,14,Data_W[2],2);
	
	//读取从机Flash中的数据
	Read_Data (0x000000,Data_R,3);
	//显示从Flash中读取的数据
	OLED_ShowHexNum (3,8,Data_R[0],2);
	OLED_ShowHexNum (3,11,Data_R[1],2);
	OLED_ShowHexNum (3,14,Data_R[2],2);

	
	while(1)
	{
		
	}
}

最终效果:主机向从机的Flash写入一组数据,并从从机的Flash中将数据读取,对写入数据和读出数据进行对比,验证实验是否成功。

注意事项

  • 在进行Flash的写操作前必须进行写使能和扇区擦除;
  • 在进行Flash的写操作后必须进行等待忙状态的处理;
  • W25Q64的地址为24位,要分三次发送。

二、硬件SPI

SPI外设

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

SPI外设的基本结构图

对单片机SPI外设的初始化配置如上图顺序。

主模式全双工连续传输

此模式下SPI传输数据速度更快,会提前将下一个数据传入发送缓冲器中。

非连续传输

非连续传输,速度相对较慢,但是时序结构简单,不易出错。

程序

先对W25Q64模块进行编写,W25Q64.c文件:

复制代码
#include "stm32f10x.h"                  // Device header
#include "W25Q64_Ins.h"

//对于从机的选择还是选择使用软件触发
void SPI_SS(uint8_t Value)
{
	GPIO_WriteBit (GPIOA ,GPIO_Pin_4,(BitAction)Value);
}
//对从机进行选择
void MySPI_Start(void)
{
	SPI_SS(0);
}
//取消对从机的选择
void MySPI_Stop(void)
{
	SPI_SS(1);
}

//函数:对从机进行初始化配置
void W25Q64_Init(void)
{
	//打开APB2总线上GPIOA、SPI1时钟
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA ,ENABLE);
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_SPI1 ,ENABLE);
	
	//对PA4进行初始化配置,配置为推挽输出模式
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_4;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init (GPIOA ,&GPIO_InitStruct );
	
	//对PA5、PA7进行初始化配置,配置为复用推挽输出模式
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init (GPIOA ,&GPIO_InitStruct );
	
	//对PA6进行初始化配置,配置为上拉输入模式
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init (GPIOA ,&GPIO_InitStruct );
	
	//对SPI外设单元进行初始化配置
	SPI_InitTypeDef SPI_InitStructure;
	//将 STM32选择为主机
	SPI_InitStructure.SPI_Mode =SPI_Mode_Master;
	//将从机选择置为软件触发
	SPI_InitStructure.SPI_NSS =SPI_NSS_Soft ;
	//选择SPI模式0
	SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge ;
	SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low;
	//选择128分频
	SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_128 ;
	//
	SPI_InitStructure.SPI_CRCPolynomial =0;
	//将传输数据帧选择为8位
	SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b;
	//选择两线全双工传输模式
	SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex ;
	//选择高位先行
	SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB;
	//初始化SPI
	SPI_Init(SPI1 ,&SPI_InitStructure);
	//打开SPI1通道
	SPI_Cmd(SPI1 ,ENABLE );
}

//将下面这些函数的发送部分用"SPI_I2S_SendData()",接收部分用"SPI_I2S_ReceiveData()"
void Read_ID(uint8_t *MF,uint16_t *ID)
{
	MySPI_Start ();
	SPI_I2S_SendData(SPI1,W25Q64_JEDEC_ID);
	*MF = SPI_I2S_ReceiveData(SPI1);
	*ID =  SPI_I2S_ReceiveData(SPI1);
	*ID <<= 8;
	*ID |=  SPI_I2S_ReceiveData(SPI1); 
	MySPI_Stop ();
}

void Write_Enable(void)
{
	MySPI_Start ();
	SPI_I2S_SendData(SPI1,W25Q64_WRITE_ENABLE);
	MySPI_Stop ();
}

void Wait_Busy(void)
{
	uint32_t Timeout = 100000;
	MySPI_Start ();
	SPI_I2S_SendData(SPI1,W25Q64_READ_STATUS_REGISTER_1);
	while((SPI_I2S_ReceiveData(SPI1) & 0x01) == 0x01)
	{
		Timeout --;
		if(Timeout == 0 )
		{
			break ;
		}
	}
	MySPI_Stop ();
}

//在写入数据前对TXE标志位进行判断
void Page_Program(uint32_t address,uint8_t *DataArray,uint16_t count)
{
	uint8_t i;
	Write_Enable ();
	MySPI_Start ();
	SPI_I2S_SendData (SPI1,W25Q64_PAGE_PROGRAM);
	SPI_I2S_SendData (SPI1,address >> 16);
	SPI_I2S_SendData (SPI1,address >> 8);
	SPI_I2S_SendData (SPI1,address);
	for(i=0;i<count;i++)
	{
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
		SPI_I2S_SendData (SPI1,DataArray [i]);
	}
	MySPI_Stop ();
	
	Wait_Busy ();
}


void Sector_Erase(uint32_t address)
{
	Write_Enable ();
	
	MySPI_Start ();
	SPI_I2S_SendData (SPI1,W25Q64_SECTOR_ERASE_4KB);
	SPI_I2S_SendData (SPI1,address >> 16);
	SPI_I2S_SendData (SPI1,address >> 8);
	SPI_I2S_SendData (SPI1,address);
	MySPI_Stop ();
	
	Wait_Busy ();
}

//在读取数据前对RXNE标志位进行判断
void Read_Data(uint32_t address,uint8_t *DataArray,uint16_t count)
{
	uint16_t i;
	MySPI_Start ();
	SPI_I2S_SendData (SPI1,W25Q64_READ_DATA);
	SPI_I2S_SendData (SPI1,address >> 16);
	SPI_I2S_SendData (SPI1,address >> 8);
	SPI_I2S_SendData (SPI1,address);
	for(i=0;i<count;i++)
	{
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
		DataArray[i] = SPI_I2S_ReceiveData(SPI1);
	}
	MySPI_Stop ();
}

W25Q64.h文件:

复制代码
#ifndef _W25Q64_H
#define _W25Q64_H
void W25Q64_Init(void);
void Read_ID(uint8_t *MF,uint16_t *ID);
void Write_Enable(void);
void Wait_Busy(void);
void Page_Program(uint32_t address,uint8_t *DataArray,uint16_t count);
void Sector_Erase(uint32_t address);
void Read_Data(uint32_t address,uint8_t *DataArray,uint16_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
#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

#endif

最后在主函数中调用各个函数实现数据的写入读取,main.c文件:

复制代码
#include "stm32f10x.h"    // Device header
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;
uint8_t Data_W[3]={0x09,0x77,0x88};
uint8_t Data_R[3];
int main(void)
{	
	OLED_Init();
	W25Q64_Init ();
	
	OLED_ShowString (1,1,"MID:   DID:");
	Read_ID(&MID,&DID);
	OLED_ShowHexNum (1,5,MID,2);
	OLED_ShowHexNum (1,12,DID,4);
	
	OLED_ShowString (2,1,"Data_W:");
	OLED_ShowString (3,1,"Data_R:");
	
	Sector_Erase (0x000000);
	Page_Program (0x000000,Data_W,3);
	
	OLED_ShowHexNum (2,8,Data_W[0],2);
	OLED_ShowHexNum (2,11,Data_W[1],2);
	OLED_ShowHexNum (2,14,Data_W[2],2);
	
	Read_Data (0x000000,Data_R,3);
	
	OLED_ShowHexNum (3,8,Data_R[0],2);
	OLED_ShowHexNum (3,11,Data_R[1],2);
	OLED_ShowHexNum (3,14,Data_R[2],2);

	
	while(1)
	{
		
	}
}

最终效果与软件I2C一致。

注意事项

  • 对从机的选择用软件触发比较方便;
  • 硬件SPI的SS需要配置为推挽输出、MOSI,SCL配置为复用输出模式、MISO配置为上拉输入模式。
相关推荐
飞睿科技2 小时前
从一颗雷达芯片到一台生命体征仪:飞睿 UWB 雷达的“一芯两用”
嵌入式硬件·物联网·uwb
伟大的前程3 小时前
Flash 只能将比特从 1 改写为 0,无法从 0 改回 1
单片机
Zw-awa3 小时前
点亮第一盏 LED:STM32 的 GPIO 到底在控制什么
stm32
剑指offer.4 小时前
嵌入式硬件-ARM芯片的启动
c语言·嵌入式硬件·嵌入式
Kuakewei8884 小时前
1.5V升压3V专用芯片PW5100在单节干电池供电中的边界条件
嵌入式硬件
DaTou大头4 小时前
STM32第六篇
单片机·嵌入式·电路基础
Kuakewei8885 小时前
1.5V升压3.3V专用芯片PW5100直通关断与PW5103真关断分析
嵌入式硬件
威嵌神州5 小时前
VxWorks6.9 BSP 适配:飞腾 D2000/FT2000/4 平台、驱动开发
驱动开发·嵌入式硬件·fpga开发·系统架构
一直C6 小时前
ARM裸机开发Day04|IMX6ULL汇编&C点灯实操,Makefile、链接脚本、段解析
嵌入式硬件·imx6ull·交叉编译
女神下凡6 小时前
端侧 AI 产品 有什么产品
人工智能·嵌入式硬件