STM32学习笔记八——I2C通信&SPI通信

I2C通信协议

I2C总线简介

STM32学习笔记八——I2C通信&SPI通信_第1张图片

硬件电路

STM32学习笔记八——I2C通信&SPI通信_第2张图片

I2C时序基本单元

STM32学习笔记八——I2C通信&SPI通信_第3张图片
STM32学习笔记八——I2C通信&SPI通信_第4张图片
STM32学习笔记八——I2C通信&SPI通信_第5张图片
STM32学习笔记八——I2C通信&SPI通信_第6张图片

I2C时序

STM32学习笔记八——I2C通信&SPI通信_第7张图片
STM32学习笔记八——I2C通信&SPI通信_第8张图片
STM32学习笔记八——I2C通信&SPI通信_第9张图片

MPU6050

MPU6050简介

STM32学习笔记八——I2C通信&SPI通信_第10张图片

MPU6050参数

STM32学习笔记八——I2C通信&SPI通信_第11张图片

MPU6050硬件电路

STM32学习笔记八——I2C通信&SPI通信_第12张图片

MPU6050框图

STM32学习笔记八——I2C通信&SPI通信_第13张图片

软件I2C读写MPU6050

软件I2C是通过GPIO引脚模拟I2C协议来进行通信的方法。以下是使用软件I2C读写MPU6050传感器的步骤:

初始化GPIO引脚: 配置用于模拟I2C通信的GPIO引脚,包括SDA和SCL。

编写I2C函数: 编写函数来模拟I2C通信协议,包括发送启动条件、发送停止条件、发送数据、接收数据等操作。

编写MPU6050函数: 使用编写的I2C函数,实现MPU6050传感器的读写函数,包括初始化、读取加速度、读取陀螺仪等操作。

硬件I2C读写MPU6050

硬件I2C是使用STM32的硬件I2C外设来进行通信的方法。以下是使用硬件I2C读写MPU6050传感器的步骤:

初始化I2C外设: 配置I2C外设,包括SDA、SCL引脚,以及I2C通信速率等。

编写I2C读写函数: 使用HAL库或直接操作寄存器,编写I2C读写函数来发送启动条件、发送停止条件、发送数据、接收数据等操作。

编写MPU6050函数: 使用编写的I2C函数,实现MPU6050传感器的读写函数,包括初始化、读取加速度、读取陀螺仪等操作。

软件I2C读写MPU6050

第一步:连接线路

第二步:编写程序

实验现象:在OLED屏幕上的第一行显示(MPU6050的ID号固定为0x68)ID号:68,第二行到第四行的左边内容是加速度传感器的输出数据,分别是X轴、Y轴和Z轴的加速度;右边三个是陀螺仪传感器的输出数据,分别是X轴、Y轴和Z轴的角速度。可以改变MPU6050的姿态,这些数据会发生变化。
main.c文件

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

uint8_t ID;
//加速度和角速度的值
int16_t AX, AY, AZ, GX, GY, GZ;

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1, 1, "ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	while (1)
	{
		//读取数据
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

MyI2C.c文件

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

//引脚配置
//配置 I2C 时钟线的函数,BitValue 决定是否将时钟线拉高或拉低。
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction )BitValue );
	Delay_us(10);
}

//配置 I2C 数据线的函数,BitValue 决定是否将数据线拉高或拉低。
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction )BitValue );
	Delay_us(10);
}

//读取 I2C 数据线的函数,返回当前数据线的状态(高或低)。
uint8_t MyI2C_R_SDA(void )
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB ,GPIO_Pin_11);
	Delay_us(10);
	return BitValue ;
}

//初始化I2C
void MyI2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ;//开漏输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOB ,&GPIO_InitStructure );
	
	GPIO_SetBits (GPIOB ,GPIO_Pin_10 | GPIO_Pin_11);
}

//发送 I2C 起始信号的函数。在 I2C 通信中,起始信号由数据线从高电平跳变到低电平,而时钟线保持高电平。
void MyI2C_Start(void)
{	
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

//发送 I2C 停止信号的函数。在 I2C 通信中,停止信号由数据线从低电平跳变到高电平,而时钟线保持高电平。
void MyI2C_Stop(void)
{	
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

//发送一个字节数据的函数。依次将字节的每个位(从高位到低位)写入数据线,并通过时钟线的上升沿和下降沿来进行数据传输。
void MyI2C_SendByte(uint8_t Byte)
{	
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
	//i=0,取Byte的最高位,结果为0x80(1)或0x00(0)
	//i=1,取Byte的次高位,结果为0x40(1)或0x00(0)
	//……依次取Byte的8位
}

//接收一个字节数据的函数。依次读取数据线的每个位,并在时钟线的上升沿和下降沿之间采样数据。
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i,Byte = 0x00;
	MyI2C_W_SDA(1);
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1);
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
		MyI2C_W_SCL(0);
	}
	return Byte;
}

//发送应答信号的函数。根据 ACKBit 的值决定是否将数据线拉低(发送应答)或拉高(发送非应答)。
void MyI2C_SendAck(uint8_t AckBit)
{	
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

//接收应答信号的函数。读取数据线的状态以确定是否接收到应答信号。
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;
}

MyI2C.h文件

#ifndef __MYI2C_H
#define __MYI2C_H

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);

#endif

MPU6050.c文件

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"

//宏定义了 MPU6050 设备的 I2C 地址
#define MPU6050_ADDRESS		0xD0

//写入 MPU6050 寄存器的函数。该函数通过 I2C 通信写入数据到指定寄存器。
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

//读取 MPU6050 寄存器的函数。该函数通过 I2C 通信从指定寄存器读取数据。
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
	MyI2C_ReceiveAck();
	Data = MyI2C_ReceiveByte();
	MyI2C_SendAck(1);
	MyI2C_Stop();
	
	return Data;
}

//初始化 MPU6050 模块的函数。
void MPU6050_Init(void)
{
	//初始化 I2C 总线
	MyI2C_Init();
	//向 MPU6050_PWR_MGMT_1 寄存器写入值 0x01,电源管理寄存器1
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	//向 MPU6050_PWR_MGMT_2 寄存器写入值 0x00,电源管理寄存器2
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
//具体寄存器的配置参考数据手册

//获取 MPU6050 设备的ID。通过读取 "Who Am I" 寄存器,可以确定设备是否正常连接。
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

//获取 MPU6050 的数据。该函数读取加速度计和陀螺仪的数据,并将其保存在传入的指针变量中
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

MPU6050.h文件

#ifndef __MPU6050_H
#define __MPU6050_H

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);

#endif

MPU6050_Reg.h文件

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif

/*
#ifndef __MPU6050_REG_H 和 #define __MPU6050_REG_H:这是条件编译指令,用于防止头文件被重复包含。
这样的写法确保在同一编译单元(源文件)中只会包含一次这个头文件。

MPU6050_SMPLRT_DIV、MPU6050_CONFIG、MPU6050_GYRO_CONFIG、MPU6050_ACCEL_CONFIG:这些宏定义了一些
用于配置 MPU6050 的寄存器地址。它们表示采样率分频、配置寄存器、陀螺仪配置寄存器和加速度计配置寄
存器的地址。

MPU6050_ACCEL_XOUT_H 到 MPU6050_GYRO_ZOUT_L:这些宏定义了加速度计和陀螺仪数据的寄存器地址。用于
从 MPU6050 中读取传感器数据。

MPU6050_TEMP_OUT_H 和 MPU6050_TEMP_OUT_L:这两个宏定义了温度数据的寄存器地址。

MPU6050_PWR_MGMT_1 和 MPU6050_PWR_MGMT_2:这两个宏定义了电源管理寄存器的地址,用于配置 MPU6050 的
电源管理设置。

MPU6050_WHO_AM_I:这个宏定义了 "Who Am I" 寄存器的地址,用于验证设备的身份,通常用于确认与设备的通
信是否正常。

#endif:结束条件编译的指令。
*/

I2C通信外设

I2C外设简介

STM32学习笔记八——I2C通信&SPI通信_第14张图片

I2C框图

STM32学习笔记八——I2C通信&SPI通信_第15张图片

I2C基本结构

STM32学习笔记八——I2C通信&SPI通信_第16张图片

主机发送

STM32学习笔记八——I2C通信&SPI通信_第17张图片

主机接收

STM32学习笔记八——I2C通信&SPI通信_第18张图片

软件/硬件波形对比

STM32学习笔记八——I2C通信&SPI通信_第19张图片

硬件I2C读写MPU6050

第一步:连接线路

第二步:编写程序

实验现象:在OLED屏幕上的第一行显示(MPU6050的ID号固定为0x68)ID号:68,第二行到第四行的左边内容是加速度传感器的输出数据,分别是X轴、Y轴和Z轴的加速度;右边三个是陀螺仪传感器的输出数据,分别是X轴、Y轴和Z轴的角速度。可以改变MPU6050的姿态,这些数据会发生变化。
main.c文件

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

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1, 1, "ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

MPU6050.c文件

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS		0xD0

//等待指定的 I2C 事件发生
//它采用两个参数,第一个是 I2C 外设的指针(比如 I2C1 或 I2C2),第二个是要等待的 I2C 事件(比如 I2C_EVENT_MASTER_MODE_SELECT)
//使用了一个超时计数器,如果在一定的时间内没有检测到指定的 I2C 事件,则会退出等待
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
}

//用于向 MPU6050 的寄存器写入数据
//它采用两个参数,第一个是要写入数据的寄存器地址,第二个是要写入的数据
//
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	//生成 I2Cx 通信的起始条件
	I2C_GenerateSTART(I2C2, ENABLE);
	//等待主模式选择事件(MASTER MODE SELECT)。这个函数会一直等待,直到 I2C2 外设进入主模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	//发送地址字节以选择从设备,指示是写入操作(I2C_Direction_Transmitter)
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	//等待主模式的发送模式选择事件(MASTER TRANSMITTER MODE SELECTED)。这个函数会一直等待,直到 I2C2 进入主模式的发送模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	//通过 I2Cx 外设发送一个数据字节。
	I2C_SendData(I2C2, RegAddress);
	//等待主模式的字节发送事件(MASTER BYTE TRANSMITTING)。这个函数会一直等待,直到当前字节被发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
	
	I2C_SendData(I2C2, Data);
	//等待主模式的字节发送完成事件(MASTER BYTE TRANSMITTED)。这个函数会一直等待,直到当前字节发送完成
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	//生成 I2Cx 通信的停止条件
	I2C_GenerateSTOP(I2C2, ENABLE);
}

//从 MPU6050 的寄存器读取数据
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	//生成 I2C2 通信的起始条件(START condition),启动通信
	I2C_GenerateSTART(I2C2, ENABLE);
	//等待主模式选择事件(MASTER MODE SELECT)。这个函数会一直等待,直到 I2C2 外设进入主模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	//发送 7 位的从设备地址,并指示是写入操作(I2C_Direction_Transmitter)。这里的 MPU6050_ADDRESS 是设备地址,低位是写入操作位
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	//等待主模式的发送模式选择事件(MASTER TRANSMITTER MODE SELECTED)。这个函数会一直等待,直到 I2C2 进入主模式的发送模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	//通过 I2C2 外设发送要读取的寄存器地址
	I2C_SendData(I2C2, RegAddress);
	//等待主模式的字节发送事件(MASTER BYTE TRANSMITTED)。这个函数会一直等待,直到当前字节被发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	//生成 I2C2 通信的起始条件(START condition),重新启动通信
	I2C_GenerateSTART(I2C2, ENABLE);
	//等待主模式选择事件(MASTER MODE SELECT)。这个函数会一直等待,直到 I2C2 外设重新进入主模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	//发送 7 位的从设备地址,并指示是读取操作(I2C_Direction_Receiver)。这里的 MPU6050_ADDRESS 是设备地址,低位是读取操作位
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
	//等待主模式的接收模式选择事件(MASTER RECEIVER MODE SELECTED)。这个函数会一直等待,直到 I2C2 进入主模式的接收模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
	
	//禁用应答位,表示这是最后一个要接收的字节
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	//生成 I2C2 通信的停止条件(STOP condition),结束通信
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	//等待主模式的字节接收事件(MASTER BYTE RECEIVED)。这个函数会一直等待,直到当前字节被接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
	//将从 I2C2 外设接收到的数据存储在 Data 变量中
	Data = I2C_ReceiveData(I2C2);
	
	//启用应答位,为将来的通信准备
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

//初始化 MPU6050 模块
void MPU6050_Init(void)
{
	//启用了 I2C2 和 GPIOB 的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//初始化 GPIOB 引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//复用开漏模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//初始化 I2C2 外设
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C ;//将 I2C2 配置为 I2C 模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;//设置 I2C2 的通信速率为 50000Hz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2 ;//设置 I2C2 的时钟占空比为 2
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//启用 I2C2 的应答功能
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置 I2C2 从设备地址的长度为 7 位
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;//设置 I2C2 主设备自身的地址为 0x00(主设备不用于接收)
	I2C_Init(I2C2, &I2C_InitStructure);
	
	//使能 I2C2 外设
	I2C_Cmd(I2C2, ENABLE);
	
	//将 MPU6050 的电源管理寄存器1配置为唤醒状态。这意味着传感器会从休眠模式中唤醒,开始正常工作
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	//将 MPU6050 的电源管理寄存器2配置为正常工作模式,不应用任何自动休眠或唤醒功能
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	//将采样率分频器寄存器配置为将采样率从默认的 1kHz 降低到约 100Hz,因为 0x09 对应的分频系数是 10
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
	/*
	将配置寄存器配置为以下设置:
		低通滤波器带宽为 5 Hz。
		外部同步禁用。
	*/
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
	/*
	这会将陀螺仪配置寄存器配置为以下设置:
		量程范围为 ±2000°/s。即陀螺仪的最大测量范围为 ±2000°/s。
		不应用自检功能。
	*/
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
	/*
	将加速度计配置寄存器配置为以下设置:
		量程范围为 ±16g。即加速度计的最大测量范围为 ±16g。
		不应用自检功能。
	*/
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

//获取 MPU6050 的器件 ID
//调用了 MPU6050_ReadReg() 函数,读取了 MPU6050 的 MPU6050_WHO_AM_I 寄存器,该寄存器存储了 MPU6050 的唯一 ID
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

//这个函数用于获取 MPU6050 的加速度和陀螺仪数据
//通过一系列的 MPU6050_ReadReg() 函数来读取对应的寄存器数据,然后将高位和低位数据合并成一个 16 位的数据,最终得到加速度和陀螺仪的数据
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	//从寄存器 MPU6050_ACCEL_XOUT_H 读取加速度计 X 轴的高字节数据,并将其存储在 DataH 中
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	//从寄存器 MPU6050_ACCEL_XOUT_L 读取加速度计 X 轴的低字节数据,并将其存储在 DataL 中
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	//将 DataH 左移 8 位,然后与 DataL 进行按位或操作,得到一个 16 位的加速度计 X 轴数据,将结果存储在 *AccX 指向的变量中
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	//从寄存器 MPU6050_GYRO_XOUT_H 读取陀螺仪 X 轴的高字节数据,并将其存储在 DataH 中
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	//从寄存器 MPU6050_GYRO_XOUT_L 读取陀螺仪 X 轴的低字节数据,并将其存储在 DataL 中
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	//将 DataH 左移 8 位,然后与 DataL 进行按位或操作,得到一个 16 位的陀螺仪 X 轴数据,将结果存储在 *GyroX 指向的变量中
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

MPU6050.h文件

#ifndef __MPU6050_H
#define __MPU6050_H

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);

#endif

SPI通信协议

SPI通信

STM32学习笔记八——I2C通信&SPI通信_第20张图片
I2C(内部集成电路)和 SPI(串行外设接口)都是嵌入式系统(包括 STM32 微控制器)中使用的流行串行通信协议。 它们服务于不同的目的并具有不同的特征。 以下是STM32上I2C和SPI的异同:

相似之处:

  1. 串行通信: I2C和SPI都是串行通信协议,用于微控制器和各种外设之间交换数据。

  2. 主从架构: 两种协议都支持主从架构,其中微控制器(主)控制一个或多个外围设备(从)。

  3. 同步通信: 两种协议都是同步的,这意味着它们使用时钟信号来同步主设备和从设备之间的数据传输。

  4. 寻址: 两种协议都有寻址机制。 I2C 设备使用 7 位或 10 位地址进行寻址,而 SPI 通常使用片选 (CS) 信号来选择目标设备。

  5. 硬件支持: STM32 微控制器具有对 I2C 和 SPI 的内置硬件支持,无需大量软件开销即可实现高效通信。

差异:

  1. 总线配置:

    • I2C:I2C 使用 2 线总线(SCL 用于时钟,SDA 用于数据),多个设备连接到同一总线。 它需要在总线上有上拉电阻。
    • SPI:SPI 使用单独的线来传输数据(MOSI 和 MISO)、时钟(SCK)和片选(CS)。 它支持与多个设备通信,但每个设备都需要专用的CS线。
  2. 数据传输:

    • I2C:I2C支持多主多从通信。 数据以具有开始和停止条件的帧形式传输,使其适合较慢的数据速率和更简单的设置。
    • SPI:SPI 通常比 I2C 更快,并且支持全双工通信。 它适用于更高的数据速率和需要更多带宽的应用。
  3. 时钟信号:

    • I2C:I2C 使用在总线上的所有设备之间共享的单个时钟信号。
    • SPI:SPI 对连接到总线的每个从设备使用单独的时钟信号。
  4. 比特率:

    • I2C:I2C支持各种比特率,不同时钟速度的设备可以在同一总线上共存。
    • SPI:SPI的时钟速度由主机决定,总线上的所有设备都以相同的速度运行。
  5. 电线数量:

    • I2C:与 SPI 相比,I2C 需要更少的电线 (2)。
    • SPI:由于数据线和时钟线是分开的,SPI 通常需要更多线(4 条或更多)。
  6. 协议开销:

    • I2C:由于启动和停止条件,I2C 具有更多的协议开销,使其比 SPI 稍慢。
    • SPI:SPI 的协议开销较少,并且通常比 I2C 更快。

综上所述,I2C和SPI都是STM32生态系统中有价值的通信协议,它们之间的选择取决于数据速率、设备数量和硬件复杂性等因素。 I2C 适用于较慢的数据速率和可以共享总线的设备,而 SPI 提供更高的数据速率和更灵活的配置选项,但需要更多的电线(SS)。

硬件电路

STM32学习笔记八——I2C通信&SPI通信_第21张图片

移位示意图

STM32学习笔记八——I2C通信&SPI通信_第22张图片

SPI时序基本单元

STM32学习笔记八——I2C通信&SPI通信_第23张图片
STM32学习笔记八——I2C通信&SPI通信_第24张图片
SPI 模式 0:

  • CPOL(时钟极性): 时钟线的空闲状态为低电平。
  • CPHA(时钟相位): 数据在时钟的第一个边沿(上升沿)捕获,并在第二个边沿(下降沿)传播。

STM32 SPI 交换一个字节的过程(模式0):

  1. 配置:

    • 配置 SPI 外设:设置通信模式(模式 0)、时钟极性、时钟相位、数据顺序(MSB 或 LSB 在前)以及数据帧格式(8 或 16 位)。
  2. 片选(CS)激活:

    • 如果多个从设备连接到 SPI 总线,则通过将其 CS 线驱动为低电平来选择目标从设备。
  3. 数据传输:

    • 准备要传输的数据字节。
  4. 开始计时:

    • 时钟线(SCK)从低电平(CPOL = 0)开始。
  5. 数据交换 - 上升沿 (CPHA = 0):

    • 在时钟的第一个上升沿,主机输出数据字节的最高有效位 (MSB)。
    • 从机在时钟的上升沿采样数据。
  6. 时钟转换 - 下降沿:

    • 时钟从低电平转换为高电平 (CPHA = 0)。
  7. 数据移位 - 下降沿:

    • 在时钟的下降沿,主器件移位数据字节的剩余位(从 MSB 到 LSB),同时从器件移入接收到的数据。
  8. 时钟转换 - 上升沿:

    • 时钟从高电平转换为低电平。
  9. 数据接收 - 上升沿:

    • 在时钟的下一个上升沿,主机从从机接收数据字节的最低有效位 (LSB)。
  10. 时钟转换 - 下降沿:

    • 时钟从低电平转换为高电平。
  11. 数据移位和完成 - 下降沿:

    • 时钟继续切换,主设备和从设备都会移位剩余的位。
    • 当时钟再次达到低电平时,完整的数据字节被交换。
  12. 芯片选择停用:

    • 如有必要,通过将 CS 线驱动为高电平来停用 CS 线,以结束与所选从机的通信。
  13. 完成:

    • 当前数据字节的 SPI 通信已完成。

在 SPI 模式 0(CPOL = 0,CPHA = 0)下,数据在时钟的第一个边沿(上升沿)移入,数据在第二个边沿(下降沿)移出。 此模式可确保数据在主设备和从设备之间正确采样和传播,同时遵守指定的时钟极性和相位。
STM32学习笔记八——I2C通信&SPI通信_第25张图片
SPI 模式 1:

  • CPOL(时钟极性): 时钟线的空闲状态为低电平。
  • CPHA(时钟相位): 数据在时钟的上升沿捕获并在下降沿传播。

STM32 SPI 交换一个字节的过程(模式1):

  1. 配置:

    • 配置 SPI 外设:设置通信模式(模式 1)、时钟极性、时钟相位、数据顺序(MSB 或 LSB 在前)以及数据帧格式(8 或 16 位)。
  2. 片选(CS)激活:

    • 如果多个从设备连接到 SPI 总线,则通过将其 CS 线驱动为低电平来选择目标从设备。
  3. 数据传输:

    • 准备要传输的数据字节。
  4. 开始计时:

    • 时钟线(SCK)从低电平(CPOL = 0)开始。
  5. 数据交换:

    • 在时钟的上升沿(CPHA = 0),主机输出数据字节的最高有效位(MSB)。
    • 从机在时钟的上升沿采样数据。
  6. 时钟转换:

    • 时钟从低电平转换为高电平 (CPHA = 0)。
  7. 数据转移:

    • 在时钟的下降沿,主器件移位数据字节的剩余位(从 MSB 到 LSB),同时从器件移入接收到的数据。
  8. 时钟转换:

    • 时钟从高电平转换为低电平。
  9. 数据接收:

    • 在时钟的下一个上升沿,主机从从机接收数据字节的最低有效位 (LSB)。
  10. 时钟转换:

    • 时钟从低电平转换为高电平。
  11. 数据转移和完成:

    • 时钟继续切换,主设备和从设备都会移位剩余的位。
    • 当时钟再次达到高电平时,完整的数据字节被交换。
  12. 芯片选择停用:

    • 如有必要,通过将 CS 线驱动为高电平来停用 CS 线,以结束与所选从机的通信。
  13. 完成:

    • 当前数据字节的 SPI 通信已完成。

在 SPI 模式 1 中,数据在时钟的上升沿捕获并在下降沿传播。 该模式常用于各种应用,并且在数据同步方面提供了灵活性。
STM32学习笔记八——I2C通信&SPI通信_第26张图片
SPI 模式 2:

  • CPOL(时钟极性): 时钟线的空闲状态为高电平。
  • CPHA(时钟相位): 数据在时钟的第一个边沿(上升沿)捕获,并在第二个边沿(下降沿)传播。

STM32 SPI 交换一个字节的过程(模式2):

  1. 配置:

    • 配置 SPI 外设:设置通信模式(模式 2)、时钟极性、时钟相位、数据顺序(MSB 或 LSB 在前)以及数据帧格式(8 或 16 位)。
  2. 片选(CS)激活:

    • 如果多个从设备连接到 SPI 总线,则通过将其 CS 线驱动为低电平来选择目标从设备。
  3. 数据传输:

    • 准备要传输的数据字节。
  4. 开始计时:

    • 时钟线(SCK)从高电平(CPOL = 1)开始。
  5. 数据交换 - 上升沿 (CPHA = 0):

    • 在时钟的第一个上升沿,主机输出数据字节的最高有效位 (MSB)。
    • 从机在时钟的上升沿采样数据。
  6. 时钟转换 - 下降沿:

    • 时钟从高电平转换为低电平 (CPHA = 0)。
  7. 数据移位 - 下降沿:

    • 在时钟的下降沿,主器件移位数据字节的剩余位(从 MSB 到 LSB),同时从器件移入接收到的数据。
  8. 时钟转换 - 上升沿:

    • 时钟从低电平转换为高电平。
  9. 数据接收 - 上升沿:

    • 在时钟的下一个上升沿,主机从从机接收数据字节的最低有效位 (LSB)。
  10. 时钟转换 - 下降沿:

    • 时钟从高电平转换为低电平。
  11. 数据移位和完成 - 下降沿:

    • 时钟继续切换,主设备和从设备都会移位剩余的位。
    • 当时钟再次达到低电平时,完整的数据字节被交换。
  12. 芯片选择停用:

    • 如有必要,通过将 CS 线驱动为高电平来停用 CS 线,以结束与所选从机的通信。
  13. 完成:

    • 当前数据字节的 SPI 通信已完成。

在 SPI 模式 2(CPOL = 1,CPHA = 0)下,时钟线在空闲状态期间处于高电平状态,数据在时钟上升沿(CPHA = 0)移入。 然后,数据在时钟的下降沿移出,确保正确的数据采样和传播,同时遵守指定的时钟极性和相位。
STM32学习笔记八——I2C通信&SPI通信_第27张图片
SPI 模式 3:

  • CPOL(时钟极性): 时钟线的空闲状态为高电平。
  • CPHA(时钟相位): 数据在时钟的第二个边沿(下降沿)捕获并在第一个边沿(上升沿)传播。

STM32 SPI 交换一个字节的过程(模式3):

  1. 配置:

    • 配置 SPI 外设:设置通信模式(模式 3)、时钟极性、时钟相位、数据顺序(MSB 或 LSB 在前)以及数据帧格式(8 或 16 位)。
  2. 片选(CS)激活:

    • 如果多个从设备连接到 SPI 总线,则通过将其 CS 线驱动为低电平来选择目标从设备。
  3. 数据传输:

    • 准备要传输的数据字节。
  4. 开始计时:

    • 时钟线(SCK)从高电平(CPOL = 1)开始。
  5. 数据移位 - 下降沿 (CPHA = 1):

    • 在时钟的第一个下降沿,主机移出数据字节的最高有效位 (MSB)。
    • 从机在时钟的下降沿采样数据。
  6. 时钟转换 - 上升沿:

    • 时钟从低电平转换为高电平 (CPHA = 1)。
  7. 数据交换 - 上升沿:

    • 在时钟的上升沿,主机输出数据字节的剩余位(从 MSB 到 LSB)。
    • 从机在时钟的上升沿移入接收到的数据。
  8. 时钟转换 - 下降沿:

    • 时钟从高电平转换为低电平。
  9. 数据接收 - 下降沿:

    • 在时钟的下降沿,主机从从机接收数据字节的最低有效位 (LSB)。
  10. 时钟转换 - 上升沿:

    • 时钟从低电平转换为高电平。
  11. 数据移位和完成 - 上升沿:

    • 时钟继续切换,主设备和从设备都会移位剩余的位。
    • 当时钟再次达到高电平时,完整的数据字节被交换。
  12. 芯片选择停用:

    • 如有必要,通过将 CS 线驱动为高电平来停用 CS 线,以结束与所选从机的通信。
  13. 完成:

    • 当前数据字节的 SPI 通信已完成。

在 SPI 模式 3(CPOL = 1,CPHA = 1)下,时钟线在空闲状态期间处于高电平状态,数据在时钟的第一个沿(下降沿)移出,而数据在时钟的第一个沿(下降沿)移入。 时钟的第二个沿(上升沿)。 此模式可确保正确的数据采样和传播,同时遵守指定的时钟极性和相位。

SPI时序

STM32学习笔记八——I2C通信&SPI通信_第28张图片
STM32学习笔记八——I2C通信&SPI通信_第29张图片
STM32学习笔记八——I2C通信&SPI通信_第30张图片

W25Q64

W25Q64简介

STM32学习笔记八——I2C通信&SPI通信_第31张图片

硬件电路

STM32学习笔记八——I2C通信&SPI通信_第32张图片

W25Q64框图

STM32学习笔记八——I2C通信&SPI通信_第33张图片

Flash操作注意事项

STM32学习笔记八——I2C通信&SPI通信_第34张图片

软件SPI读写W25Q64

软件SPI是通过GPIO引脚模拟SPI协议来进行通信的方法。以下是使用软件SPI读写W25Q64 Flash存储器的步骤:

初始化GPIO引脚: 配置用于模拟SPI通信的GPIO引脚,包括SCK、MISO、MOSI和CS。

编写SPI函数: 编写函数来模拟SPI通信协议,包括发送和接收数据的逻辑。这些函数可能包括发送一个位、发送一个字节、接收一个字节等。

编写W25Q64函数: 使用编写的SPI函数,实现W25Q64 Flash存储器的读写函数,包括写使能、擦除、编程等操作。

硬件SPI读写W25Q64

硬件SPI是使用STM32的硬件SPI外设来进行通信的方法。以下是使用硬件SPI读写W25Q64 Flash存储器的步骤:

初始化SPI外设: 配置SPI外设,包括SCK、MISO、MOSI引脚,以及SPI通信模式、数据速率等。

编写SPI读写函数: 使用HAL库或直接操作寄存器,编写SPI读写函数来发送和接收数据。

编写W25Q64函数: 使用编写的SPI函数,实现W25Q64 Flash存储器的读写函数,包括写使能、擦除、编程等操作。

软件SPI读写W25Q64

第一步:连接线路

第二步:编写程序

实验现象:在OLED屏幕上,第一行显示为制造商ID和设备ID,第二行为W:写入数据,第三行为R:读取数据
main.c文件

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

//定义了全局变量MID和DID,用于存储从W25Q64 Flash存储器读取的制造商ID和设备ID
uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_Init();
	
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	//调用W25Q64_ReadID函数从W25Q64中读取制造商ID和设备ID
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	/*
	擦除扇区并编程数据:首先,调用W25Q64_SectorErase函数擦除W25Q64的一个扇区(地址为0x000000)。
	然后,调用W25Q64_PageProgram函数将写入数组ArrayWrite中的数据编程到W25Q64的相同扇区中(地址为0x000000)
	*/
	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);
	
	//调用W25Q64_ReadData函数从W25Q64的相同扇区中(地址为0x000000)读取数据,并将读取的数据存储在数组ArrayRead中
	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)
	{
		
	}
}

MySPI.c文件

#include "stm32f10x.h"                  // Device header

//用于设置片选信号(SS)。当SS为高电平时,MOSI线会被拉低,从而选择从设备
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit (GPIOA,GPIO_Pin_4 ,(BitAction )BitValue );
}

//用于设置时钟信号(SCK)。当SCK为高电平时,每个时钟周期都会传输一个位
void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit (GPIOA,GPIO_Pin_5 ,(BitAction )BitValue );
}

//用于设置主设备输出从设备输入(MOSI)线。当MOSI线被拉低时,它会将数据位发送到从设备
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit (GPIOA,GPIO_Pin_7 ,(BitAction )BitValue );
}

//用于读取从设备的MISO线。如果MISO线为高电平,那么从设备已经成功接收了一个位
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit (GPIOA ,GPIO_Pin_6);
}

//初始化SPI
void MySPI_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 );	
	
	MySPI_W_SS(1);
	MySPI_W_SCK(0);
}

//用于开始SPI通信。它通过将片选信号设置为高电平来启动SPI
void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

//用于停止SPI通信。它通过将片选信号设置为低电平来停止SPI
void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

//用于通过SPI交换一个字(8位)。它通过循环发送和接收数据来实现字的交换
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;
	
	for(i = 0; i < 8; i ++)
	{
		MySPI_W_MOSI(ByteSend & (0x80 >>i));
		MySPI_W_SCK(1);
		if ( MySPI_R_MISO() == 1){ByteReceive |= (0x80 >>i);}
		MySPI_W_SCK(0);
	}
	
	return ByteReceive;
}

MySPI.h文件

#ifndef __MYSPI_H
#define __MYSPI_H

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

#endif

W25Q64.c文件

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

//用于初始化与W25Q64 Flash存储器通信所需要的SPI接口,调用了MySPI_Init()函数来初始化SPI通信
void W25Q64_Init(void)
{
	MySPI_Init();
}

/*
这个函数用于读取W25Q64 Flash存储器的制造商ID(MID)和设备ID(DID)。
函数首先启动SPI通信,发送命令来获取ID信息,然后读取制造商ID和设备ID,
	将设备ID左移8位并与另一个字节合并,最终获取完整的设备ID。
函数结束后关闭SPI通信。
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

/*
这个函数发送命令给W25Q64 Flash存储器,使其允许写入操作。
函数启动SPI通信,发送写使能命令,然后关闭SPI通信。
*/
void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

/*
这个函数用于等待W25Q64 Flash存储器完成其内部操作,例如擦除、编程等。
函数发送读取状态寄存器命令,然后循环读取状态寄存器的忙标志位,直到标志位为0或达到超时限制。
函数结束后关闭SPI通信。
*/
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

/*
这个函数用于向W25Q64 Flash存储器的指定页写入数据。
函数首先发送写使能命令,然后启动SPI通信,发送页编程命令和地址,然后循环发送数据数组中的数据。
函数结束后关闭SPI通信,并调用W25Q64_WaitBusy()等待存储器完成操作。
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/*
这个函数用于擦除W25Q64 Flash存储器的指定扇区。
函数首先发送写使能命令,然后启动SPI通信,发送扇区擦除命令和地址。
函数结束后关闭SPI通信,并调用W25Q64_WaitBusy()等待存储器完成擦除。
*/
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/*
这个函数用于从W25Q64 Flash存储器的指定地址开始读取一定数量的数据。
函数首先启动SPI通信,发送读取数据命令和地址,然后循环接收数据并存储到数据数组中。
函数结束后关闭SPI通信。
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

W25Q64.h文件

#ifndef __W25Q64_H
#define __W25Q64_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
#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_WRITE_ENABLE:启用写操作使能命令。
W25Q64_WRITE_DISABLE:禁用写操作使能命令。
W25Q64_READ_STATUS_REGISTER_1:读取状态寄存器1命令。
W25Q64_READ_STATUS_REGISTER_2:读取状态寄存器2命令。
W25Q64_WRITE_STATUS_REGISTER:写入状态寄存器命令。
W25Q64_PAGE_PROGRAM:页编程命令。
W25Q64_QUAD_PAGE_PROGRAM:四线编程命令。
W25Q64_BLOCK_ERASE_64KB:64KB块擦除命令。
W25Q64_BLOCK_ERASE_32KB:32KB块擦除命令。
W25Q64_SECTOR_ERASE_4KB:4KB扇区擦除命令。
W25Q64_CHIP_ERASE:整片擦除命令。
W25Q64_ERASE_SUSPEND:擦除暂停命令。
W25Q64_ERASE_RESUME:擦除恢复命令。
W25Q64_POWER_DOWN:进入掉电模式命令。
W25Q64_HIGH_PERFORMANCE_MODE:高性能模式命令。
W25Q64_CONTINUOUS_READ_MODE_RESET:连续读模式复位命令。
W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID:释放掉电和高性能模式下设备ID读取命令。
W25Q64_MANUFACTURER_DEVICE_ID:制造商设备ID读取命令。
W25Q64_READ_UNIQUE_ID:读取唯一ID命令。
W25Q64_JEDEC_ID:JEDEC ID读取命令。
W25Q64_READ_DATA:读取数据命令。
W25Q64_FAST_READ:快速读取命令。
W25Q64_FAST_READ_DUAL_OUTPUT:快速双输出读取命令。
W25Q64_FAST_READ_DUAL_IO:快速双输入输出读取命令。
W25Q64_FAST_READ_QUAD_OUTPUT:快速四输出读取命令。
W25Q64_FAST_READ_QUAD_IO:快速四输入输出读取命令。
W25Q64_OCTAL_WORD_READ_QUAD_IO:八线字节读取四输入输出读取命令。
W25Q64_DUMMY_BYTE:虚拟的填充字节,用于在读取操作中填充空位。
*/

SPI通信外设

SPI外设简介

STM32学习笔记八——I2C通信&SPI通信_第35张图片
I2S也是一种3引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦I2S标准,MSB和LSB对齐标准,以及PCM标准。它在半双工通讯中,可以工作在主和从2种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。

SPI框图

STM32学习笔记八——I2C通信&SPI通信_第36张图片
通常SPI通过4个引脚与外部器件相连:
● MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
● MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
● SCK:串口时钟,作为主设备的输出,从设备的输入
● NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。一旦被使能(SSOE位),NSS引脚也可以作为输出引脚,并在SPI处于主模式时拉低;此时,所有的SPI设备,如果它们的NSS引脚连接到主设备的NSS引脚,则会检测到低电平,如果它们被设置为NSS硬件模式,就会自动进入从设备状态。当配置为主设备、NSS配置为输入引脚(MSTR=1,SSOE=0)时,如果NSS被拉低,则这个SPI设备进入主模式失败状态:即MSTR位被自动清除,此设备进入从模式。

具体可以参考STM32参考手册23 串行外设接口(SPI)

SPI基本结构

STM32学习笔记八——I2C通信&SPI通信_第37张图片

主模式全双工连续传输

STM32学习笔记八——I2C通信&SPI通信_第38张图片

非连续传输

STM32学习笔记八——I2C通信&SPI通信_第39张图片

软件/硬件波形对比

STM32学习笔记八——I2C通信&SPI通信_第40张图片

硬件SPI读写W25Q64

第一步:连接线路

第二步:编写程序

实验现象:在OLED屏幕上,第一行显示为制造商ID和设备ID,第二行为W:写入数据,第三行为R:读取数据
main.c文件

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

//定义了全局变量MID和DID,用于存储从W25Q64 Flash存储器读取的制造商ID和设备ID
uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_Init();
	
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	//调用W25Q64_ReadID函数从W25Q64中读取制造商ID和设备ID
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	/*
	擦除扇区并编程数据:首先,调用W25Q64_SectorErase函数擦除W25Q64的一个扇区(地址为0x000000)。
	然后,调用W25Q64_PageProgram函数将写入数组ArrayWrite中的数据编程到W25Q64的相同扇区中(地址为0x000000)
	*/
	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);
	
	//调用W25Q64_ReadData函数从W25Q64的相同扇区中(地址为0x000000)读取数据,并将读取的数据存储在数组ArrayRead中
	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)
	{
		
	}
}

MySPI.c文件

#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit (GPIOA,GPIO_Pin_4 ,(BitAction )BitValue );
}

void MySPI_Init(void)
{
	//开启GPIOA和SPI1的时钟,以使能这两个外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 ,ENABLE);
	
	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 );	
	
	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 );	
	
	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_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master ;//配置SPI工作在主模式(主设备)
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex ;//配置SPI为双线全双工通信
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b ;//配置SPI的数据大小为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB ;//配置SPI的数据传输方式为高位优先
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128 ;//配置SPI的波特率预分频器为128,用于设定SPI通信速率
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge ;//配置SPI的时钟极性和相位,这里为第一个边沿(上升沿)采样,CPHA=1
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low ;//配置SPI的时钟极性,这里为低电平时钟,CPOL=1
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft ;//配置SPI的NSS信号为软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7 ;//配置SPI的CRC多项式
	SPI_Init (SPI1 ,&SPI_InitStructure);
	
	//使能SPI1外设
	SPI_Cmd (SPI1 ,ENABLE );
	
	//将从片选信号(Slave Select,NSS)置高,通常在初始化时将其置高,表示没有选择从设备进行通信
	MySPI_W_SS(1);
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	// 等待直到发送缓冲区为空,表示可以发送数据
	while (SPI_I2S_GetFlagStatus (SPI1 ,SPI_I2S_FLAG_TXE ) != SET );
	/*
	在这一步中,代码等待直到发送缓冲区为空,这意味着可以将新的数据发送到SPI总线上。
	当发送缓冲区为空时,标志位 SPI_I2S_FLAG_TXE 将被设置为1。这一步确保在发送新数据之前,
	之前的数据已经被完整地发送出去。
	*/
	
	// 向SPI发送数据
	SPI_I2S_SendData (SPI1 ,ByteSend);
	
	// 等待直到接收缓冲区有数据,表示已接收到数据
	while (SPI_I2S_GetFlagStatus (SPI1 ,SPI_I2S_FLAG_RXNE ) != SET );
	/*
	这一步等待直到接收缓冲区有数据,这表示从SPI总线上接收到了数据。当接收缓冲区有数据时,
	标志位 SPI_I2S_FLAG_RXNE 将被设置为1。
	*/
	
	// 返回从SPI接收到的数据
	return SPI_I2S_ReceiveData (SPI1);
}

MySPI.h文件

#ifndef __MYSPI_H
#define __MYSPI_H

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

#endif

W25Q64.c文件

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

//用于初始化与W25Q64 Flash存储器通信所需要的SPI接口,调用了MySPI_Init()函数来初始化SPI通信
void W25Q64_Init(void)
{
	MySPI_Init();
}

/*
这个函数用于读取W25Q64 Flash存储器的制造商ID(MID)和设备ID(DID)。
函数首先启动SPI通信,发送命令来获取ID信息,然后读取制造商ID和设备ID,
	将设备ID左移8位并与另一个字节合并,最终获取完整的设备ID。
函数结束后关闭SPI通信。
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

/*
这个函数发送命令给W25Q64 Flash存储器,使其允许写入操作。
函数启动SPI通信,发送写使能命令,然后关闭SPI通信。
*/
void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

/*
这个函数用于等待W25Q64 Flash存储器完成其内部操作,例如擦除、编程等。
函数发送读取状态寄存器命令,然后循环读取状态寄存器的忙标志位,直到标志位为0或达到超时限制。
函数结束后关闭SPI通信。
*/
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

/*
这个函数用于向W25Q64 Flash存储器的指定页写入数据。
函数首先发送写使能命令,然后启动SPI通信,发送页编程命令和地址,然后循环发送数据数组中的数据。
函数结束后关闭SPI通信,并调用W25Q64_WaitBusy()等待存储器完成操作。
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/*
这个函数用于擦除W25Q64 Flash存储器的指定扇区。
函数首先发送写使能命令,然后启动SPI通信,发送扇区擦除命令和地址。
函数结束后关闭SPI通信,并调用W25Q64_WaitBusy()等待存储器完成擦除。
*/
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/*
这个函数用于从W25Q64 Flash存储器的指定地址开始读取一定数量的数据。
函数首先启动SPI通信,发送读取数据命令和地址,然后循环接收数据并存储到数据数组中。
函数结束后关闭SPI通信。
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

W25Q64.h文件

#ifndef __W25Q64_H
#define __W25Q64_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
#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

你可能感兴趣的:(stm32,学习,笔记)