串口,I2C,SPI的特点与区别

名称引脚双工时钟电平设备
UARTTX,RX全双工异步单端点对点
I2CSCL,SDA半双工同步单端多设备
SPISCLK,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终止
}

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值