软件I2C是通过GPIO引脚模拟I2C协议来进行通信的方法。以下是使用软件I2C读写MPU6050传感器的步骤:
初始化GPIO引脚: 配置用于模拟I2C通信的GPIO引脚,包括SDA和SCL。
编写I2C函数: 编写函数来模拟I2C通信协议,包括发送启动条件、发送停止条件、发送数据、接收数据等操作。
编写MPU6050函数: 使用编写的I2C函数,实现MPU6050传感器的读写函数,包括初始化、读取加速度、读取陀螺仪等操作。
硬件I2C是使用STM32的硬件I2C外设来进行通信的方法。以下是使用硬件I2C读写MPU6050传感器的步骤:
初始化I2C外设: 配置I2C外设,包括SDA、SCL引脚,以及I2C通信速率等。
编写I2C读写函数: 使用HAL库或直接操作寄存器,编写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:结束条件编译的指令。
*/
实验现象:在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
I2C(内部集成电路)和 SPI(串行外设接口)都是嵌入式系统(包括 STM32 微控制器)中使用的流行串行通信协议。 它们服务于不同的目的并具有不同的特征。 以下是STM32上I2C和SPI的异同:
相似之处:
串行通信: I2C和SPI都是串行通信协议,用于微控制器和各种外设之间交换数据。
主从架构: 两种协议都支持主从架构,其中微控制器(主)控制一个或多个外围设备(从)。
同步通信: 两种协议都是同步的,这意味着它们使用时钟信号来同步主设备和从设备之间的数据传输。
寻址: 两种协议都有寻址机制。 I2C 设备使用 7 位或 10 位地址进行寻址,而 SPI 通常使用片选 (CS) 信号来选择目标设备。
硬件支持: STM32 微控制器具有对 I2C 和 SPI 的内置硬件支持,无需大量软件开销即可实现高效通信。
差异:
总线配置:
数据传输:
时钟信号:
比特率:
电线数量:
协议开销:
综上所述,I2C和SPI都是STM32生态系统中有价值的通信协议,它们之间的选择取决于数据速率、设备数量和硬件复杂性等因素。 I2C 适用于较慢的数据速率和可以共享总线的设备,而 SPI 提供更高的数据速率和更灵活的配置选项,但需要更多的电线(SS)。
STM32 SPI 交换一个字节的过程(模式0):
配置:
片选(CS)激活:
数据传输:
开始计时:
数据交换 - 上升沿 (CPHA = 0):
时钟转换 - 下降沿:
数据移位 - 下降沿:
时钟转换 - 上升沿:
数据接收 - 上升沿:
时钟转换 - 下降沿:
数据移位和完成 - 下降沿:
芯片选择停用:
完成:
在 SPI 模式 0(CPOL = 0,CPHA = 0)下,数据在时钟的第一个边沿(上升沿)移入,数据在第二个边沿(下降沿)移出。 此模式可确保数据在主设备和从设备之间正确采样和传播,同时遵守指定的时钟极性和相位。
SPI 模式 1:
STM32 SPI 交换一个字节的过程(模式1):
配置:
片选(CS)激活:
数据传输:
开始计时:
数据交换:
时钟转换:
数据转移:
时钟转换:
数据接收:
时钟转换:
数据转移和完成:
芯片选择停用:
完成:
在 SPI 模式 1 中,数据在时钟的上升沿捕获并在下降沿传播。 该模式常用于各种应用,并且在数据同步方面提供了灵活性。
SPI 模式 2:
STM32 SPI 交换一个字节的过程(模式2):
配置:
片选(CS)激活:
数据传输:
开始计时:
数据交换 - 上升沿 (CPHA = 0):
时钟转换 - 下降沿:
数据移位 - 下降沿:
时钟转换 - 上升沿:
数据接收 - 上升沿:
时钟转换 - 下降沿:
数据移位和完成 - 下降沿:
芯片选择停用:
完成:
在 SPI 模式 2(CPOL = 1,CPHA = 0)下,时钟线在空闲状态期间处于高电平状态,数据在时钟上升沿(CPHA = 0)移入。 然后,数据在时钟的下降沿移出,确保正确的数据采样和传播,同时遵守指定的时钟极性和相位。
SPI 模式 3:
STM32 SPI 交换一个字节的过程(模式3):
配置:
片选(CS)激活:
数据传输:
开始计时:
数据移位 - 下降沿 (CPHA = 1):
时钟转换 - 上升沿:
数据交换 - 上升沿:
时钟转换 - 下降沿:
数据接收 - 下降沿:
时钟转换 - 上升沿:
数据移位和完成 - 上升沿:
芯片选择停用:
完成:
在 SPI 模式 3(CPOL = 1,CPHA = 1)下,时钟线在空闲状态期间处于高电平状态,数据在时钟的第一个沿(下降沿)移出,而数据在时钟的第一个沿(下降沿)移入。 时钟的第二个沿(上升沿)。 此模式可确保正确的数据采样和传播,同时遵守指定的时钟极性和相位。
软件SPI是通过GPIO引脚模拟SPI协议来进行通信的方法。以下是使用软件SPI读写W25Q64 Flash存储器的步骤:
初始化GPIO引脚: 配置用于模拟SPI通信的GPIO引脚,包括SCK、MISO、MOSI和CS。
编写SPI函数: 编写函数来模拟SPI通信协议,包括发送和接收数据的逻辑。这些函数可能包括发送一个位、发送一个字节、接收一个字节等。
编写W25Q64函数: 使用编写的SPI函数,实现W25Q64 Flash存储器的读写函数,包括写使能、擦除、编程等操作。
硬件SPI是使用STM32的硬件SPI外设来进行通信的方法。以下是使用硬件SPI读写W25Q64 Flash存储器的步骤:
初始化SPI外设: 配置SPI外设,包括SCK、MISO、MOSI引脚,以及SPI通信模式、数据速率等。
编写SPI读写函数: 使用HAL库或直接操作寄存器,编写SPI读写函数来发送和接收数据。
编写W25Q64函数: 使用编写的SPI函数,实现W25Q64 Flash存储器的读写函数,包括写使能、擦除、编程等操作。
实验现象:在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:虚拟的填充字节,用于在读取操作中填充空位。
*/
I2S也是一种3引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦I2S标准,MSB和LSB对齐标准,以及PCM标准。它在半双工通讯中,可以工作在主和从2种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。
通常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)
实验现象:在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