STM32-SPI通信协议

目录

一:什么是通信协议?

二:电路结构

1.硬件电路

2:移位

3:时序图

4.交换字节

三:W25Q64简介

硬件电路:​编辑

存储器地址划分

Dlash操作注意事项

状态寄存器

SPI指令集

四:软件SPI读写W25Q64

1.W25Q64接线

2.整体框架

3,通信引脚配置

[4.写SPI的3个时序基本单元 (3个拼图)](#4.写SPI的3个时序基本单元 (3个拼图))

[5. W25Q64的硬件驱动层](#5. W25Q64的硬件驱动层)


一:什么是通信协议?

  • SPI(Serial Peripheral Interface)是一种同步串行通信协议,在嵌入式中光放应用于微控制与外设间的数据传输。它具有高速、全双工特点。
  • 4根通信线:SCK(Serial Clock),MOSI(Master Output Slave Input),MISO(Master Input Slave Ouput ), SS(Slave Select)
  • 同步、全双工(数据发送和数据接收各占一条线)
  • 支持总线挂载多设备(一主多从)

二:电路结构

  • SCK(Serial Clock):时钟信号,由主设备产生,控制数据传输节奏
  • MOSI(Master Output Slave Input):主设备输出、从设备输入数据线,在从模式下接收数据
  • MISO(Master Input Slave Ouput ):主设备输入,从设备输出数据线,在主模式下接收数据
  • SS(Slave Select):从设备选择线,主设备用它选特定从设备通信

1.硬件电路

对于输出,我们用配置推挽输出,高低电平均有很强的驱动能力,这就使得SPI引脚信号的下降沿和上升沿非常迅速 。在SPI协议里有一条规定:当从机SS引脚为高电平(从机没被选中),它的MIOS引脚必须切换为高阻态(这样就防止,一条线有多个输出而导致的电平冲突问题了),SS为低电平时,MISO才允许变为推挽输出。

2:移位

3:时序图

4.交换字节

三:W25Q64简介

W25Q64系列是一种低成本、小型化、使用简单的非易失性存储器,常用于数据存储、字库存储、固定程序存储等场景。

存储介质:Onr Flash闪存

硬件电路:

存储器地址划分

  • 一整个存储空间,首先划分为若干块,对于每一块又划分为若干扇区
  • SPI控制逻辑:左边是通信引脚与主控芯片相连,主控芯片通过SPI协议,把指令和数据发给控制逻辑,控制逻辑就会自动操作电路来完成我i们想要的功能。
  • 控制逻辑上有一个状态存储器,
  • 写控制逻辑,与外部的WP引脚相连

Dlash操作注意事项

1.写入操作时:

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改变位0,不能由0改为1
  • 写入数据前必须先擦除,擦除后。所有数据变为1
  • 擦除必须按最擦除单元进行
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

2.读取操作时:

  • 直接调用读取时序、无需使能、无需额外操作、没有页的限制、读取操作结束后不会进入忙状态,但不能在忙状态时读取。

状态寄存器

  • BUSY

BUSY是状态寄存器(SO)中的一个只读位,当设备执行页程序、扇区擦除、块擦除、芯片擦除或写状态寄存器指令时,它被设置为1状态。

在此期间,设备将忽略除读状态寄存器和擦除暂停指令之外的其他指令(参见交流特性中的tw, tPP, tSE, be和tCE)。当程序、擦除或写状态寄存器指令完成时,BUSY位将被清除为O状态,表明设备已准备好接受进一步的指令。

  • 写使能锁存WEL (Write Enable Latch)

写使能锁存WEL (Write Enable Latch)是状态寄存器S1中的一个只读位,在执行写使能指令后被置为1。

当设备被禁止写时,WEL状态位被清除为O。写禁用状态发生在上电或以下任何指令之后:写禁用、页程序、扇区擦除、块擦除、芯片擦除和写状态寄存器。

SPI指令集

  • Write Enable:写使能,指令码是06(先起始,发送字节,第一个字节是发送方向,发送0x06指令就行了)
  • Read Status Register-1:读状态寄存器1(起始、交换字节发送指令码05)
  • Page Program:页编程,就是写数据(起始、交换字节发送指令02继续交换发送地址23-16、15-8、7-0用来指定地址。最后写入数据D7-D0了)
  • Sector Erase (4KB):指定扇区擦除(起始、发生指令20,发送扇区)

四:软件SPI读写W25Q64

1.W25Q64接线

CS片选------PA4

DO从机输出------PA6

CLK时钟------PA5

DI从机输入------PA7

2.整体框架

  • 先建一个SPI的模块,主要通信引脚封装、初始化以及SPI通信的3个拼图(起始、终止和交换一个字节)
  • 基于SPI层,再建一个W25Q64的模块,在这个模块里调用SPI的拼图,来拼接各种指令和功能的完整时序(比如写使能、擦除、页编程、读数据等)------W25Q64的硬件驱动层
  • 最后在主函数里,我们调用驱动层的函数来完成我们想要的功能

3,通信引脚配置

cpp 复制代码
/*引脚配置层*/

/**
  * 函    数: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~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue为1时,需要置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电平并返回
}

4.写SPI的3个时序基本单元 (3个拼图)

  • 起始信号(SS置低电平信号)

    cpp 复制代码
    /**
      * 函    数:SPI起始
      * 参    数:无
      * 返 回 值:无
      */
    void MySPI_Start(void)
    {
    	MySPI_W_SS(0);				//拉低SS,开始时序
    }
  • 终止信号(SS置高电平信号)

    cpp 复制代码
    /**
      * 函    数:SPI终止
      * 参    数:无
      * 返 回 值:无
      */
    void MySPI_Stop(void)
    {
    	MySPI_W_SS(1);				//拉高SS,终止时序
    }
  • 交换一个字节(SPI的核心)

由时序图可知:在SS下降沿之后,我们开始交换字节(先SS下降沿,再SCK上升沿,再移入数据,再SCK下降沿,再移除数据)

cpp 复制代码
/**
  * 函    数: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次,依次交换每一位数据
	{
		/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/
		MySPI_W_MOSI(!!(ByteSend & (0x80 >> i)));	//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
		MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据
		if (MySPI_R_MISO()){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
															//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
		MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据
	}
	
	return ByteReceive;								//返回接收到的一个字节数据
}

5. W25Q64的硬件驱动层

1.宏定义

cpp 复制代码
#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

2.Flash操作 (根据SPI指令集来写代码)

cpp 复制代码
/**
  * 函    数:W25Q64初始化
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_Init(void)
{
	MySPI_Init();					//先初始化底层的SPI
}

/**
  * 函    数:W25Q64读取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终止
}
相关推荐
技术干货贩卖机1 小时前
0基础 | 看懂原理图Datasheet 系列1
笔记·stm32·单片机·嵌入式硬件·学习
-一杯为品-2 小时前
【51单片机】程序实验16.DS1302时钟
嵌入式硬件·mongodb·51单片机
柒十三.2 小时前
江科大51单片机笔记【14】直流电机驱动(PWM)
笔记·嵌入式硬件·51单片机
星光始终闪耀3 小时前
第十一届蓝桥杯单片机国赛
单片机·蓝桥杯
待什么青丝4 小时前
【TMS570LC4357】之工程创建
c语言·单片机
summer__77774 小时前
3.3.2 Proteus第一个仿真图
单片机·proteus
Nav.5 小时前
STM32 Bootloader理解
stm32·单片机·嵌入式硬件
mftang5 小时前
STM32 CAN模块原理与应用详解
stm32·单片机·嵌入式硬件
@陽光總在風雨後5 小时前
调试正常 ≠ 运行正常:Keil5中MicroLIB的“量子态BUG”破解实录
c语言·arm开发·stm32·单片机·嵌入式硬件
Ronin-Lotus5 小时前
嵌入式硬件篇---手柄控制控制麦克纳姆轮子
嵌入式硬件·ps2·麦克纳姆轮子