名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
UART | TX,RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL,SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK,MISO,MOSI,CS | 全双工 | 同步 | 单端 | 多设备 |
UART
特点:简单,成本低,使用方便,可实现两个设备之前的通信,可以使用串口协议实现单片机和PC的通信,从而对单片机程序进行调试。
需要共电,即同VCC和GND。电平不一致时,需要加电平转换芯片。
协议内容
硬件方面,需要同VCC和GND,TX与RX对接。
软件方面,串口协议规定一个数据帧需要有起始位,数据位,校验位,停止位。其中校验位可以省略。
当设备空闲时,TX固定为高电平,起始位的作用就是拉低电平,将TX置为低电平,告诉设备要开始传送数据了,然后就可以传送数据位和校验位,在传送完成后,停止位会再次将总线拉高,即将TX置为高电平,意味着设备再次回到空闲状态。
至于是否有校验位,数据位有几位,则交由软件设置。
注意,串口协议规定了数据低位先行,例如要传输00001111,则数据位为11110000,若我们设置了无校验位,则数据帧就为0111100001,其中第一个0为起始位,最后一个1为停止位。
示例
void Serial_Init()
{
RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1 ,ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE );
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_9 ;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init (GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitSture;
USART_InitSture.USART_BaudRate =9600;
USART_InitSture.USART_HardwareFlowControl =USART_HardwareFlowControl_None ;
USART_InitSture.USART_Mode =USART_Mode_Tx ;
USART_InitSture.USART_Parity =USART_Parity_No ;
USART_InitSture.USART_StopBits =USART_StopBits_1 ;
USART_InitSture.USART_WordLength =USART_WordLength_8b ;
USART_Init (USART1 ,&USART_InitSture);
USART_Cmd (USART1 ,ENABLE );
}
I2C
特点:有数据应答,支持多设备挂载(一主多从,多主多从)。
协议内容
硬件方面,I2C规定所有设备的SCL连接在一起,SDA连接在一起,SCL和SDA均设置为开漏输出模式,SCL和SDA各添加一个上拉电阻,阻值一般在4.7kΩ左右。
软件方面,在一主多从模型中,主机拥有对SCL和SDA的控制权,而从机不允许控制SCL,并在只有在获得允许时才能短暂控制SDA
协议方面,和串口一样,I2C也需要一个起始信号,但是因为I2C有两条总线,所以它的起始信号就不是一位,而是一个规定好的条件了。
起始条件:SCL高电平期间,SDA从高电平转到低电平
结束条件:SCL高电平期间,SDA从低电平转到高电平
知道了起始条件和结束条件,那么I2C是如何发送数据的呢?
在起始条件之后,即SCL高电平期间,SDA从高电平转到低电平之后,SCL也会被拉低,这时候SCL和SDA都处于低电平状态,而在SCL低电平期间,就可以开始传输数据了,此时主机可以将数据放入SDA(高位先行),然后释放SCL,使SCL回到高电平状态,此时从机就可以读取SDA的数据(注意,在发送数据未结束时若SCL处于高电平,则SDA不允许发生电平变化)。如此循环8次,即可发送一个字节的数据。
接收数据同理,只不过放数据在SDA上的对象变成了从机,读取数据的对象变成了主机。值得注意的是,主机在接收数据之前,需要释放SDA,将SDA的控制权交给从机。
应答机制
上文提到过,I2C有个特点,即拥有应答机制。
在主机接收一个字节数据之后,会在下一个时钟发送一个数据位来应答,这一位倘若为0,则表示已接收,为1则为未接收。这一应答我们称之为发送应答。
有发送应答,自然也有接收应答。接收应答也是同理,主机在发送一个字节数据后,会放开SDA,然后在下一个时钟接收一个数据位,倘若为0则为已发送,为1则为未发送。
数据和操作规定
I2C的数据读写操作主要有指定位置写,指定位置读和当前位置读三种。
不管是什么操作,第一个字节都是从机地址和读写位。从机地址大部分都为7位,加上读写位刚好组成一个字节。从机地址基本由厂商给出,可以在芯片手册里查到,其中最后一位还可以通过引脚灵活切换。一个I2C总线里不允许挂载两个同地址的从机。
至于读写位,0表示主机要进行写入操作,1表示主机要进行读出操作。
此时已经发送了一个字节,依照协议规定会产生一个应答位,然后继续操作。
倘若置1,在下一个时钟,主机会放开SDA,然后执行读取操作。即当前位置读
倘若置0,第二个字节,根据从机设备不同,可能是不同的数据,此时应根据手册进行编辑(一般为寄存器地址或指令控制字)。然后根据需求决定是否需要发送后续字节。这就是指定位置写。
值得着重讲的是指定位置读。
I2C的指定位置读由指定位置写和当前位置读两部分复合而成。在执行指定位置读时,第一次发送的字节为从机地址和读写位,这个读写位置0,代表我们要进行写入操作,然后第二次发送的字节为我们想要读取的寄存器地址。此时需重复一次起始条件,然后执行当前位置读的操作。
两个操作结合,便可完成指定位置读的操作。
软件模拟I2C示例代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit (GPIOB ,GPIO_Pin_10 ,(BitAction )BitValue );
Delay_us (10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit (GPIOB ,GPIO_Pin_11 ,(BitAction )BitValue );
Delay_us (10);
}
uint8_t MyI2C_R_SDA()
{
uint8_t BitValue;
BitValue =GPIO_ReadInputDataBit (GPIOB ,GPIO_Pin_11 );
Delay_us (10);
return BitValue ;
}
void MyI2C_Init()
{
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 );
}
void MyI2C_Start()
{
MyI2C_W_SDA(1); //将SCL和SDA都置为高电平,准备开始
MyI2C_W_SCL(1); //先置SDA,可以兼容指定位置读
MyI2C_W_SDA(0);
MyI2C_W_SCL(0); //先拉低SDA再拉低SCL,软件模拟I2C
}
void MyI2C_Stop()
{
MyI2C_W_SDA(0); //确保结束是可以翻转SDA
MyI2C_W_SCL(1);
MyI2C_W_SDA(1); //SCL高电平是SDA置高,满足I2C结束条件
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
//循环8次,每次将字节的一位送入SDA,然后驱动时钟从而把数据送入从机
for(i=0;i<8;i++)
{ MyI2C_W_SDA(Byte & (0x80>>i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte()
{
uint8_t i,Byte=0x00;
//先拉高SDA,以免主机干扰从机写入SDA
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
//此时从机已经写入,将SCL置高,主机开始读入数据
if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);}
MyI2C_W_SCL(0);
}
return Byte ;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck()
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit ;
}
硬件I2C示例代码
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
//定义等待时间,防止程序卡死
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;
}
}
}
//指定位置写,参数分别为寄存器地址和需要写入的数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE); //生成起始信号
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待事件发生
//由于I2C协议较为复杂,在使用硬件I2C时,我们需要知道某一步是否已经执行完毕
//在串口协议中,我们可以使用GetITStatus的方法获取某一标志位的信息来确定是否完成
//但I2C的复杂程度决定了它只用某一标志位来确定步骤是否完成并不标准。
//为解决这一问题,STM32规定了EVENT事件
//在执行同步时,我们只需要捕获某一EVENT是否发生即可实现同步
//如上方的I2C_EVENT_MASTER_MODE_SELECT,STM32就规定它为EV5
//每一步发生了什么事件,数据手册中有定义,而这一事件对应的参数通常存在xxxxx.i2c.c文件中
//以STM32F10系列举例,该定义就存在于stm32f10x_i2c.c中
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
//指定位置读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2, ENABLE);
return Data;
}
void MPU6050_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
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);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
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);
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
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;
}
SPI
特点:四根通信线,高速,全双工,同步,支持总线挂载多设备(一主多从)
协议内容
硬件方面,SPI规定所有设备的SCK接在一起,主机的MOSI接所有从机的MISO,主机的MISO接所有从机的MOSI,同时主机会给每一个从机设备都单独接一根CS片选线(也称SS),所有设备共地,输出引脚配置为推挽输出,输入引脚配置为上拉或浮空输入。
软件方面:(模式一)SCK的频率由主机确定,主机的移位寄存器,MOSI线,从机的移位寄存器,MISO线这四个部分构成一个回路,每产生时钟信号,在上升沿时,两个移位寄存器的数据将左移一位,最高位的数据被放入数据线上。下降沿时,数据线上的数据将被写入对方的移位寄存器最低位。
还有一个值得说明的模式零。在此模式下,CS一旦产生下降沿,MOSI和MISO就会立刻开始传输数据,即把CS的下降沿也当成一个时钟信号,在时序图上的变化便是MOSi和MISO会提前半个相位。该模式是最为常见的模式。
如此反复8次,即可实现移位寄存器的数据对换。
SPI不管是发送还是接收,都是基于数据的对换。
通信起始条件:CS从高电平转变为低电平
终止条件:CS从低电平转变为高电平
软件模拟SPI示例代码
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
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();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
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();
}
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();
}
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();
}
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();
}
硬件SPI示例代码
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
/**
* 函 数:W25Q64初始化
* 参 数:无
* 返 回 值:无
*/
void W25Q64_Init(void)
{
MySPI_Init(); //先初始化底层的SPI
}
/**
* 函 数:MPU6050读取ID号
* 参 数:MID 工厂ID,使用输出参数的形式返回
* 参 数:DID 设备ID,使用输出参数的形式返回
* 返 回 值:无
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位
*DID <<= 8; //高8位移到高位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回
MySPI_Stop(); //SPI终止
}
/**
* 函 数:W25Q64写使能
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令
MySPI_Stop(); //SPI终止
}
/**
* 函 数:W25Q64等待忙
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WaitBusy(void)
{
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页编程
* 参 数: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终止
}