STM32F103+SPI+W25Q64

一、SPI简介

        SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器和数字信号处理器和数字信号解码器之间。

SPI主从模式
        SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

SPI信号线
        SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在 时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接 口可以是3线式或4线式。

  • MOSI(Master Output Slave Input) – 主设备输出/从设备输入信号;
  • MISO(Master Input Slave Output) – 主设备输入/从设备输出信号;
  • SCLK(Serial Clock) – 串行时钟信号,由主设备产生,用来同步数据比特;
  •  SS(Slave Select)/CS(Chip Select) – 从设备片选信号,低电平有效,由主设备控制;

        主机和从机之间传输的数据与 主机产生的时钟同步。同I 2 C接口相比,SPI器件支持更高的时钟 频率。 SPI接口只能有一个主机,但可以有一个或多个从机。主机的片选信号用于选择从机。这通常是一个低电平有效信 号,拉高时从机与SPI总线断开连接。当使用多个从机时,主机需 要为每个从机提供单独的片选信号。MOSI和MISO是数据线。MOSI将数据从主机发送到从机,MISO将数 据从从机发送到主机。

二、SPI时钟极性和时钟相位

        时钟极性和时钟相位 在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期 间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为 高电平且在向低电平转变的期间,以及传输结束时CS为低电平且 在向高电平转变的期间。CPHA位选择时钟相位。根据CPHA位的状 态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根 据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选 择,有四种SPI模式可用。表1显示了这4种SPI模式。

传输的开始和结束用绿色虚线表示,采样边沿用橙色虚线表示,移位边沿用蓝色虚线表示。

三、多从机配置

3.1  常规SPI模式

         常规SPI模式:主机需要为每个从机提供单独的片选信号。一旦 主机使能(拉低)片选信号,MOSI/MISO线上的时钟和数据便可用 于所选的从机。如果使能多个片选信号,则MISO线上的数据会被 破坏,因为主机无法识别哪个从机正在传输数据。 从图中可以看出,随着从机数量的增加,来自主机的片选线的数量 也增加。这会快速增加主机需要提供的输入和输出数量,并限制 可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量,例如使用多路复用器产生片选信号。

3.2  菊花链模式

        在菊花链模式下,所有从机的片选信号连接在一起,数据从一个 从机传播到下一个从机。在此配置中,所有从机同时接收同一SPI 时钟。来自主机的数据直接送到第一个从机,该从机将数据提供 给下一个从机依此类推。 使用该方法时,由于数据是从一个从机传播到下一个从机,所以 传输数据所需的时钟周期数与菊花链中的从机位置成比例。例如 在图7所示的8位系统中,为使第3个从机能够获得数据,需要24个 时钟脉冲,而常规SPI模式下只需8个时钟脉冲。图8显示了时钟周 期和通过菊花链的数据传播。并非所有SPI器件都支持菊花链模 式。请参阅产品数据手册以确认菊花链是否可用。

 

四、W25Q64简介

W25Q64存储容量共64M-bit/ 8M-byte。

  • 1页 = 256bytes = 1024 kbit
  • 1 扇区 = 16页
  • 1 块 = 16扇区

所以 W25Q64有  32768页(pages) 或者 2048个可擦除扇区(sectors) 或者 可以说有128个可擦除块(blocks)。

注意点:

  1. 写数据之前Flash里面的数据不是0xFF就必须先擦除,然后才能写数据。擦除即将Flash里面的数据恢复为0xFF的过程。
  2. 上电后设备自动处于写禁用状态(Write Enable Latch, WEL为0,WEL是只读位)。在Page Program, Sector Erase, Block Erase, Chip Erase or Write Status Register instruction(页编程、区擦除、块擦除、芯片擦除或者写状态寄存器指令)之前必须先进行写使能指令。在编程、擦除、写状态寄存器指令完成后,WEL自动变成0
  3.  W25Q64BV是使用四线SPI兼容总线:串行时钟:(CLK),片选(CS),串行数据输入(DI),串行数据输出(DO)。标准SPI指令是MCU在CLK的上升沿使用DI输入引脚串行写指令,地址,数据到设备。在CLK的下降沿用DO输出引脚从设备读取数据或状态。

五、软件SPI读写W25Q64

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

myspi.c

#include "stm32f10x.h"     

/*引脚配置层*/

/**
  * 函    数:SPI写SS引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}

/**
  * 函    数:SPI写SCK引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平
  */
void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);		//根据BitValue,设置SCK引脚的电平
}

/**
  * 函    数:SPI写MOSI引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平
  */
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);		//根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}
/**
  * 函    数:I2C读MISO引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
  */
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			//读取MISO电平并返回
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*GPIO初始化*/
	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);					//将PA4、PA5和PA7引脚初始化为推挽输出
	
	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);					//将PA6引脚初始化为上拉输入
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
	MySPI_W_SCK(0);											//SCK默认低电平
}

/*协议层*/

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}


/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	
	for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据
	{
		MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
		MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
																//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
		MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据
	}
	
	return ByteReceive;								//返回接收到的一个字节数据
}

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 W25Q

W25Q64.c

#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终止
}

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SPI主机是一种串行外设接口的主控设备。SPI是Serial Peripheral Interface的缩写,它是一种常见的通信协议,用于在微控制器和外部设备之间进行数据交换。SPI主机负责控制和管理SPI总线上的通信过程。 SPI主机具有以下特点和功能: 1. 数据传输:SPI主机可以通过SPI总线与多个SPI从设备通信。它可以发送指令和数据给从设备,并从从设备中接收返回的数据。SPI主机通过在时钟线上产生时钟信号来控制数据的传输,同时在数据线上进行双向的数据传输。 2. 时钟控制:SPI主机在SPI总线上生成时钟信号,用于同步数据传输。SPI主机决定时钟的频率和相位,从而控制数据的传输速率和稳定性。 3. 帧同步:SPI主机还负责在数据传输的开始和结束时发送帧同步信号,以确保传输的准确性和完整性。 4. 硬件接口:SPI主机需要通过专门的硬件接口来实现与微控制器之间的连接。通常,SPI主机会提供多个SPI总线接口,以便同时与多个SPI从设备进行通信。 5. 通信协议SPI主机需要遵循SPI通信协议的规定,包括传输方式、时序要求和数据格式等。SPI主机需要具备对通信协议的理解和实现能力。 总之,SPI主机是一种在微控制器和外部设备之间进行数据交换的重要控制单元。它通过控制和管理SPI总线上的通信过程,实现与多个SPI从设备的高效、可靠的数据传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值