STM32与W25Q64的SPI通信详解(第一部分)

文章目录

  • 前言
  • SPI通信
  • 硬件电路
  • 移位过程
  • SPI时序
  • 起始与终止条件
  • 交换一个字节
  • W25Q64
  • 硬件电路
  • 框图
  • FLASH操作注意事项
  • 软件SPI读写W25Q64
  • 前言

    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

    #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

    #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

    #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

    #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

    #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

    #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的注意事项。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32与W25Q64的SPI通信详解(第一部分)

    发表评论