STM32--SPI通信与W25Q64(1)

文章目录

前言

USART串口链接入口
I2C通信链接入口

SPI通信

SPI(Serial Peripheral Interface)是一种高速的、全双工、同步的串行通信协议。通常用于连接主控芯片和外围设备,比如传感器、存储器、显示屏等。SPI使用简单,只需要几根线就可以实现进行通信。

硬件电路

主要线路:

SCLK(时钟信号) :由主设备产生,用于同步数据传输的时钟信号。
MOSI(主设备输出从设备输入) :主设备将数据发送给从设备的数据线。
MISO(主设备输入从设备输出) :从设备将数据发送给主设备的数据线。
SS/CS(片选信号):由主设备控制,用于选择要进行通信的特定设备。

上图中,主机连接着多个从机,但在通信时,只能对一个从机进行SPI通信,会通过选定的从机的片选信号SS从高电平置于低电平(其他没有选中的保持高电平)让主机与其通信。

移位过程

由于有两条传输数据线,所以SPI通信能做到同时进行发送数据和接收数据的特点。

主机和从机都由主机的波特率发生器控制着时钟信号,实现同步的传输。

首先主机会将移位寄存器的高位通过MOSI数据线传送到从机的移位寄存器的最低位;同时,从机的移位寄存器的最高位会通过MISO数据线传送到主机移位寄存器的最低位。两个移位寄存器将最高位的数据传出之后,移位寄存器就会进行向右移位,因此最低位也会腾出空间,让主机的最高位数据放到从机的最低位,从机的最低位数据放到主机的最低位。以此循环八次,就能将一个字节的数据进行转换了

SPI时序

起始与终止条件

起始条件:SS从高电平切换到低电平

终止条件:SS从低电平切换到高电平

这是片选信号,高低电平的切换代表SPI时序的开始和结束。

交换一个字节

交换一个字节(模式0)

CPOL=0:空闲状态时,SCK为低电平

CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

对于SPI通信,由于是同时进行数据传输,所以称之为字节的交换。

交换字节有4个模式,不同之处就在于空闲状态SCK是高电平还是低电平;还有一个从SCK的第一个边沿还是第二个边沿移入数据,这里将介绍模式0的交换,其他同理。

首先这里说的移入数据和移出数据,是指数据的移出会先放在MOSI数据线或者是MISO数据线上,通过一定的时间再把数据放入对方的最低位。所以,只有先移出数据,才能移入数据。
而这里的却从SCK的第一个边沿就移入数据,是因为主机和从机在SS的低边沿就进行将数据移出到MOSI和MISO上,所以会在SCK的高边沿就进行数据的移入,到了SCK的低边沿就将数据移出,依次重复八次,就将一个字节交换成功了

其他模式

交换一个字节(模式1)

CPOL=0:空闲状态时,SCK为低电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据


这是主机向选定的从机发送一个0x06的信号,由于对于从机发送的内容不关心,所以默认为0xFF。所以一般情况下,只有我们选择读取从机的数据,MISO的数据线才会有波形变化。

W25Q64

W25Q64是一款由华邦公司推出的大容量SPI FLASH产品,其容量为64Mb(8MB) 。它属于W25Q系列器件,相比普通的串行闪存硬件,在灵活性和性能方面也有更出色的表现。

W25Q64可以用于存储图片数据,字库数据、音频数据以及保存设备运行日志文件等。

该芯片将8M字节的容量分为128块,每个块包含16个扇区,每个扇区有4K字节。支持双路和四路SPI接口,具有较高的数据传输速率。

存储介质 :Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)

硬件电路

引脚 功能
VCC、GND 电源(2.7~3.6V)
CS(SS) SPI片选
CLK(SCK) SPI时钟
DI(MOSI) SPI主机输出从机输入
DO(MISO) SPI主机输入从机输出
WP 写保护
HOLD 数据保持

看黄色部分即可,左边是外部引脚接口,右边是芯片电路;

在引脚名上加上一横线表示接通时默认为低电平,VCC与GND连接时会有一个滤波电容进行滤波,还并联一个指示灯表示是否已经通电

HOLD数据保持:相当一个暂停键;当你写入数据一半时,要在别的设备使用SPI通信,那么在当前设备你就可以触发HOLD,当前设备的SPI时序就会保持静止,你就可以使用SPI对别的设备进行使用,当回到当前设备时,HOLD解除,会从禁止的SPI时序进行恢复。

WP写保护:可以通过设置特殊的写保护位来防止数据被修改。有助于保护重要数据免受意外的写操作。

框图

上面一大部分就是存储区间,将8M字节的容量分为128块,每个块包含16个扇区,每个扇区有4K字节。每个扇区还包括16个的页区,每个页区有256字节,页是最小单位。

而写入和读取都由左下角的SPI命令与控制逻辑的黑盒进行控制;

接着看到上面,是写逻辑和状态寄存器,可以通过状态寄存器来判断是否已经写入数据;

通过高压发电机来对数据进行擦除;

下面是页地址锁存器和字节地址锁存器,会对块区间通过行解码和列解码,可以判定你在哪个页区进行写入和读出;

块区域的下面是一个256字节页缓冲区,数据写入需要一定的时间,会通过缓冲区来进行缓冲。

FLASH操作注意事项

写入操作时

写入操作前,必须先进行写使能

每个数据位只能由1改写为0,不能由0改写为1

写入数据前必须先擦除,擦除后,所有数据位变为1

擦除必须按最小擦除单元进行(扇区)

连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入

写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时

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

软件SPI读写W25Q64

OLED代码链接入口

连接方式:

将数据存储在W25Q64中,通过断电测试它的存储功能;

大体思路:实现SPI通信的时序条件,接着利用SPI通信实现W25Q64时序,最后在主程序实现对FLASH的测试

MySPI.c

c 复制代码
#include "stm32f10x.h"                  // Device header

//片选电平
void MySPI_W_SS(uint8_t Byte)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)Byte);
}
//时钟电平
void MySPI_W_SCK(uint8_t Byte)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)Byte);
}
//主机发送到从机
void MySPI_W_MOSI(uint8_t Byte)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)Byte);
}
//从机发送到主机
uint8_t MySPI_R_MISO()
{
    return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

//初始化
void MySPI_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    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);
    
    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);
    
    MySPI_W_SS(1);
    MySPI_W_SCK(0);
}
//开始
void MySPI_Start()
{
    MySPI_W_SS(0);
}
//结束
void MySPI_Stop()
{
    MySPI_W_SS(1);
}
//交换字节
uint8_t MySPI_SwapByte(uint8_t SendByte)
{
    uint8_t ReceiveByte=0x00,i;
    for(i=0;i<8;i++)
    {
        MySPI_W_MOSI(SendByte&(0x80>>i)); //主发送字节
        MySPI_W_SCK(1);
        if(MySPI_R_MISO()==1)ReceiveByte|=(0x80>>i); //主接收字节
        MySPI_W_SCK(0);
        
    }
    return ReceiveByte;
}

MySPI.h

c 复制代码
#ifndef __MYSPI_H__
#define __MYSPI_H__

void MySPI_Init();
void MySPI_Start();
void MySPI_Stop();
uint8_t MySPI_SwapByte(uint8_t SendByte);

#endif

W25Q64_Ins.h

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

W25Q64.h

c 复制代码
#ifndef __W25Q64_H__
#define __W25Q64_H__

void W25Q64_Init();
void W25Q64_ReadID(uint8_t* HID,uint16_t* SID);
void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Count);

#endif

W25Q64.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "W25Q64_Ins.h"
#include "MySPI.h"

//初始化
void W25Q64_Init()
{
    MySPI_Init();
}
//读ID
void W25Q64_ReadID(uint8_t* HID,uint16_t* SID)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_JEDEC_ID);
    *HID=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    *SID=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    *SID<<=8;
    *SID|=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    MySPI_Stop();
}
//写使能
void W25Q64_WriteEnable()
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_WRITE_ENABLE);
    MySPI_Stop();
}
//等待忙状态
void W25Q64_WaitBusy()
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
    uint32_t count=10000;
    while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)&0x01)==0x01||count)
    {
        count--;
       
    }
    
    MySPI_Stop();
}
//页编程
void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Count)
{
    W25Q64_WriteEnable();
    uint16_t i;
    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,uint16_t Count)
{
    uint16_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();
}

    

对于W25Q64来说,需要先对不同的操作先写入对应的地址,

然后根据手册,写入地址和内容;

main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "W25Q64.h"
#include "OLED.h"

uint8_t HID;
uint16_t SID;

uint8_t ArrayWrite[]={0xAA,0xBB,0xCC,0xDD};
uint8_t ArrayRead[4];
int main()
{
	OLED_Init();
	W25Q64_Init();
    
    OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
    
    W25Q64_ReadID(&HID,&SID);
    OLED_ShowHexNum(1,5,HID,2);
    OLED_ShowHexNum(1,12,SID,4);
    
    W25Q64_SectorErase(0x000100);
    W25Q64_PageProgram(0x000000,ArrayWrite,4);
    
    W25Q64_ReadData(0x000000,ArrayRead,4);
    
    OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
    while(1)
    {
        
    }
}

可以通过改变擦除的地址和页编程的地址,以及存储的内容;来进行验证FLASH的注意事项。

相关推荐
智商偏低5 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen6 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森8 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白8 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D9 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术12 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt12 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘13 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang13 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n15 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件