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终止
}
相关推荐
国科安芯3 小时前
火箭传感器控制单元的抗辐照MCU选型与环境适应性验证
单片机·嵌入式硬件·架构·risc-v·安全性测试
-Springer-3 小时前
STM32 学习 —— 个人学习笔记5(EXTI 外部中断 & 对射式红外传感器及旋转编码器计数)
笔记·stm32·学习
LS_learner3 小时前
树莓派(ARM64 架构)Ubuntu 24.04 (Noble) 系统 `apt update` 报错解决方案
嵌入式硬件
来自晴朗的明天4 小时前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT4 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠4 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
代码游侠15 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
xuxg200517 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT19 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件