STM32CUBEMX硬件读写W25Q64,W25Q128,SPI

7.0 SPI原理

7.0.1 SPI

  1. SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  2. 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)主输出从输入、MISO(Master Input Slave Output)、SS(Slave Select)同步,全双工
  3. 支持总线挂载多设备(一主多从)

7.0.2硬件电路

  1. 所有SPI设备的SCK、MOSI、MISO分别连在一起
  2. 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  3. 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
  4. SS默认状态的高电平,需要用到时拉低电平
  5. MISO为了避免多个数据冲突,不使用时,为高阻态状态,即为关断输出。当ss为低电平时,才允许变为推挽输出。

7.0.3 移位示意图

移位寄存器的时钟源为主机提供的,叫波特率发生器。

1.一开始我们给一个脉冲信号。(模式1)

2.当产生上升沿的时候,所有移位寄存器向左移出一位到引脚,实际是放到了输出数据寄存器,然后1为高电平,MOSI就变为高电平,从机移位为0,低电平,所有MISO为低电平。

3.下降沿的时候,采样输入到移位寄存器的最低位。相当于下降沿时,采样数据

4.如果只是输出到从机的话,我们就不用管从机输入进来的数据就可以。

如果是输入到主机的话,我们就不用管主机输出的数据就可以。这样就完成了单项输入输出。一般不用的数据我们都为0X00或者0XFF。

7.0.4 SPI时序

读数据的时候都为下降沿

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

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

1.交换一个字节(模式0)

CPOL(Clock Polarity 时钟极性)= 0:空闲状态时,SCK为低电平

CPHA(Clock Phase 时钟相位)= 0:SCK第一个边沿移入数据,第二个边沿移出数据

解释:当SS变成低电平的时候,SCK产生变化之前,我们要先移出数据,当SCK第一个边沿时,移入数据,第二个边沿移出数据。

2.交换一个字节(模式1)

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

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

解释:CPOL=0,相当于SCK默认为低电平;当来上升沿时,主机从机同时移出数据,当下降沿时,同时采样数据。

3.交换一个字节(模式2)

CPOL(Clock Polarity 时钟极性)=1:空闲状态时,SCK为高电平

CPHA(Clock Phase 时钟相位)=0:SCK第一个边沿移入数据,第二个边沿移出数据

解释:和模式0相比,就相当于默认SCK为高电平

4.交换一个字节(模式3)

CPOL=1:空闲状态时,SCK为高电平

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

解释:和模式1相比就相当于SCK为高电平

总结:


7.1W25Q64简介

W25Q64BV.PDF

cs上面的横线,表示低电平有效。

1.W25Q40-W25Q128都是16位寻址的,所以内存就是00 00 00-FF FF FF,我们的芯片是64Mbit,所以就是到7F FF FF。

2.快分配,block,8M*1024/64=128个快,一个Block是64kb。

3.每个块的起始地址位XX0000—XXFFFF。

4.每个快在按照4KB来划分。64Kb/4kb=16个扇区(0-15),每个扇区内的地址XXX 000-XXXFFF

5.一个页是256byte,一个扇区是4*1024=16*256.所以一个扇区可以分为16页。

6.一页地址的变化XXXX00-XXXXFF。

状态寄存器需要注意:

11.1.1 BUSY

BUSY是状态寄存器(S0)中的一个只读位,当设备正在执行页面程序、扇区擦除、块擦除、芯片擦除或写状态寄存器时,它被设置为1状态 指示在此期间,设备将忽略除读取状态寄存器和擦除暂停指令外的其他指令(参见交流特性中的tW、tPP、tSE、tBE和tCE)。Wh 在程序、擦除或写入状态寄存器指令完成后,BUSY位将被清除为0状态,表明设备已经准备好接受进一步的指示。

11.1.2写入启用锁存(WEL)

写入启用锁存(WEL)是状态寄存器(S1)中的只读位,在执行写入启用指令后被设置为1。WEL状态位已被清除 当设备被禁用写入时,发送到0。写入关闭状态发生在通电或执行以下任何指令之后:写入关闭、页面程序、扇区擦除、块擦除、芯片擦除 d写入状态寄存器。

7.2 SPI函数详解

在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。

· 轮询: 最基本的发送接收函数 ,就是正常的发送数据和接收数据

· 中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能

· DMA: DMA传输SPI数据

利用SPI接口发送和接收数据主要调用以下两个函数:

HAL_StatusTypeDef  HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
HAL_StatusTypeDef  HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据

SPI发送数据函数

HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
参数:
· *hspi: 选择SPI1/2,比如&hspi1,&hspi2
· *pData : 需要发送的数据,可以为数组
· Size: 发送数据的字节数,1 就是发送一个字节数据
· Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数

SPI接收数据函数

HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
参数:
· *hspi: 选择SPI1/2,比如&hspi1,&hspi2
· *pData : 接收发送过来的数据的数组
· Size: 接收数据的字节数,1 就是接收一个字节数据
· Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数

SPI接收回调函数:

HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);
当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数
HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);
用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数

片选引脚

因为我们是软件使能片选,定义片选引脚,CS片选低电平为有效使能CS片选高电平不使能

7.3 STM32CubeMx之硬件SPI驱动W25Q64

7.3.1 硬件接口

引脚

说明

CS

片选(低电平选中) PG6

SPI2_MISO

主机输入从机输出PB4

SPI2_MOSI

主机输出从机输入PB5

SPI_SCK

时钟线PB3

7.3.2CUBE设置

7.3.3Keil 5代码编写

1.先写一下spi的驱动,CS引脚和读一个字节就好了

/* USER CODE BEGIN 1 */
//CS引脚0:启动。1:关闭
void MySPI_W_CS(uint8_t BitValue)
{
    HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port,W25Q128_CS_Pin,BitValue);
}
//SPI读一个字节
uint8_t SPI_writeOneByte(uint8_t data)
{
    uint8_t Rx_data=0;
    HAL_SPI_TransmitReceive(&hspi1,&data,&Rx_data,1,100);
    return Rx_data;
}
/* USER CODE END 1 */
/* USER CODE BEGIN Prototypes */
void MySPI_W_CS(uint8_t BitValue);
uint8_t SPI_writeOneByte(uint8_t data);
/* USER CODE END Prototypes */

2.开始写W25Q128的驱动

#include "W25Q128.h"


/*******************************************************************************
* Function Name  : W25Q128_ReadID
* Description    : 获取W25Q128设备号
*                 
*
* Input          : *MID 厂家ID号
*                  *DID 设备ID号
*                  
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_ReadID(uint8_t *MID,uint16_t *DID)
{
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_JEDEC_ID);
    *MID=SPI_writeOneByte(0xFF);
    *DID=SPI_writeOneByte(0xFF);
    *DID<<=8;
    *DID|=SPI_writeOneByte(0xFF);
    MySPI_W_CS(1);
}
/*******************************************************************************
* Function Name  : W25Q128_WriteEnable
* Description    : 写使能
*                 
*
* Input          : None
*                  None
*                  
* Output         : None 
* Return         : None
*******************************************************************************/ 	
void W25Q128_WriteEnable(void)
{
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_WRITE_ENABLE);
    MySPI_W_CS(1);	
}
/*******************************************************************************
* Function Name  : W25Q128_WaitBusy
* Description    : 等待忙碌  写入操作结束后,芯片进入忙状态,不响应新的读写操作
*                  读到的最低为为0x01时代表忙碌0x00代表不忙
*
* Input          : None
*                  None
*                  
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_WaitBusy(void)
{
    uint32_t Timeout=10000;
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_READ_STATUS_REGISTER_1);
    while ((SPI_writeOneByte(0xFF) & 0x01) == 0x01)
    {
        Timeout --;
        if (Timeout == 0)
        {
            break;
        }
    }
    MySPI_W_CS(1);	
}
/*******************************************************************************
* Function Name  : W25Q128_SectorErase
* Description    : 扇区擦除  256个字节
*                 
*
* Input          : Address:擦除地址所在的扇区。
*                  None
*                  
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_SectorErase(uint32_t Address)
{
    W25Q128_WriteEnable();
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_SECTOR_ERASE_4KB);
    SPI_writeOneByte(Address>>16);
    SPI_writeOneByte(Address>>8);
    SPI_writeOneByte(Address);
    MySPI_W_CS(1);
    W25Q128_WaitBusy();
}
/*******************************************************************************
* Function Name  : W25Q128_ChipErase
* Description    : 芯片擦除
*                 
*
* Input          : None
*                  None
*                 
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_ChipErase(void)
{
    W25Q128_WriteEnable();
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_CHIP_ERASE);
    MySPI_W_CS(1);
    W25Q128_WaitBusy();
}
/*******************************************************************************
* Function Name  : W25Q128_PageProgram
* Description    : 页写入,只能在当前地址页写入,超过会从当前页起始位置开始写。
写入前擦除当前页,芯片规则:只能1改0,不能0改1,相当于前一个数据&之后数据。
例:之前为0x01改为0x02,如果不拆除就变为:0x01&0x02=0x00。
*                 
*
* Input          : Address:写入地址
*                  *DataArray:写入数据数组
*                  Count:数组长度
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_PageProgram(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    W25Q128_WriteEnable();
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_PAGE_PROGRAM);
    SPI_writeOneByte(Address>>16);
    SPI_writeOneByte(Address>>8);
    SPI_writeOneByte(Address);
    for(int i=0;i<Count;i++)
    {
        SPI_writeOneByte(DataArray[i]);
    }
    MySPI_W_CS(1);
    W25Q128_WaitBusy();
}

/*******************************************************************************
* Function Name  : W25Q128_ReadData
* Description    : 读数据,可以读任意地址,任意个数,可跨页读
*                 
*
* Input          : Address:写入地址
*                  *DataArray:写入数据数组
*                  Count:数组长度
* Output         : None 
* Return         : None
*******************************************************************************/ 
void W25Q128_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    MySPI_W_CS(0);
    SPI_writeOneByte(W25Q128_READ_DATA);
    SPI_writeOneByte(Address>>16);
    SPI_writeOneByte(Address>>8);
    SPI_writeOneByte(Address);
    for(int i=0;i<Count;i++)
    {
        DataArray[i]=SPI_writeOneByte(0xFF);
    }
    MySPI_W_CS(1);
}

/*******************************************************************************
* Function Name  : dev_flash_write_bytes_nocheck
* Description    : 写数据 自动查询当前地址 自动越扇区写入 注意这里只能写擦除过的
*                 
*
* Input          : pdata 数据
*                  write_addr  写入地址
*                  write_length 写入长度 
* Output         : None 
* Return         : None
*******************************************************************************/ 
void dev_flash_write_bytes_nocheck(uint8_t *pdata, uint32_t write_addr, uint16_t write_length)   // 0x01 0x02 0x03  , 0x0000FE ,3
{
    // 计算当前页剩余的字节数
    uint16_t PageByte = 256 - write_addr % 256;                                                  // PageByte= 2;

    // 如果写入长度小于等于剩余字节数,则实际写入的字节数等于写入长度
    if(write_length <= PageByte)    
    {
        PageByte = write_length;
    }

    // 进入循环
    while(1)
    {
        // 调用 W25Q128_PageProgram 函数将数据写入一页闪存
        W25Q128_PageProgram(write_addr,pdata,PageByte);                                           // 1:0x0000FE=0x01,0x0000FF=0x02
        // 2:write_addr=0x000100,pdata=0x03,PageByte=1.0x000100=0x003
        // 如果写入长度等于实际写入的字节数,则退出循环
        if(write_length == PageByte)     //1=1,break
            break;
        else
        {
            // 更新数据指针、写入地址和写入长度
            pdata += PageByte;                                                                   //pdata 直接指向0x03,相当于指针右移了PageByte位
            write_addr += PageByte;                                                              //write_addr  = 0x000100
            write_length -= PageByte;                                                            //write_length=3-2=1

            // 根据剩余的写入长度确定下一次循环中实际写入的字节数
            if(write_length > 256)
            {
                PageByte = 256;
            }
            else
            {
                PageByte = write_length;                                                         //PageByte =1
            }
        }
    }
}
#ifndef __W25Q128_H__
#define __W25Q128_H__

#include "main.h"
#include "gpio.h"
#include "spi.h"
#include "stm32F4xx_hal.h"
#include <string.h>

#define W25Q128_WRITE_ENABLE								0x06
#define W25Q128_WRITE_DISABLE								0x04
#define W25Q128_READ_STATUS_REGISTER_1					0x05
#define W25Q128_READ_STATUS_REGISTER_2					0x35
#define W25Q128_WRITE_STATUS_REGISTER					0x01
#define W25Q128_PAGE_PROGRAM								0x02
#define W25Q128_QUAD_PAGE_PROGRAM						0x32
#define W25Q128_BLOCK_ERASE_64KB							0xD8
#define W25Q128_BLOCK_ERASE_32KB							0x52
#define W25Q128_SECTOR_ERASE_4KB							0x20
#define W25Q128_CHIP_ERASE									0xC7
#define W25Q128_ERASE_SUSPEND								0x75
#define W25Q128_ERASE_RESUME								0x7A
#define W25Q128_POWER_DOWN									0xB9
#define W25Q128_HIGH_PERFORMANCE_MODE					0xA3
#define W25Q128_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q128_RELEASE_POWER_DOWN_HPM_DEVICE_ID	0xAB
#define W25Q128_MANUFACTURER_DEVICE_ID					0x90
#define W25Q128_READ_UNIQUE_ID							0x4B
#define W25Q128_JEDEC_ID									0x9F
#define W25Q128_READ_DATA									0x03
#define W25Q128_FAST_READ									0x0B
#define W25Q128_FAST_READ_DUAL_OUTPUT					0x3B
#define W25Q128_FAST_READ_DUAL_IO						0xBB
#define W25Q128_FAST_READ_QUAD_OUTPUT					0x6B
#define W25Q128_FAST_READ_QUAD_IO						0xEB
#define W25Q128_OCTAL_WORD_READ_QUAD_IO				0xE3

//获取W25Q128设备号
void W25Q128_ReadID(uint8_t *MID,uint16_t *DID);
//写使能
void W25Q128_WriteEnable(void);
//等待忙碌
void W25Q128_WaitBusy(void);
//扇区擦除
void W25Q128_SectorErase(uint32_t Address);
//芯片擦除
void W25Q128_ChipErase(void);
//页写入
void W25Q128_PageProgram(uint32_t Address, uint8_t *DataArray, uint32_t Count);
//读数据
void W25Q128_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
//连续页写入
void dev_flash_write_bytes_nocheck(uint8_t *pdata, uint32_t write_addr, uint16_t write_length);
#endif

7.3.4代码验证

1.首先我们创建一些数组
/* USER CODE BEGIN PV */
uint8_t MID=0;
uint16_t DID=0;
uint8_t ArrayWrite[3];
uint8_t ArrayRead[3];
/* USER CODE END PV */
2.查看一下ID号
  W25Q128_ReadID(&MID,&DID);
  printf("W25Q128_ReadID = %X, %X\r\n",MID,DID);

现象:

3.写入一些数据,读一些数据
  for(int i=0;i<3;i++)
  {
    ArrayWrite[i]=i;
  }

  W25Q128_SectorErase(0x000000);
  HAL_Delay(10);

  W25Q128_PageProgram(0x0000FE,ArrayWrite,3);
  W25Q128_ReadData(0x0000FE,ArrayRead,3);
  for(int i=0;i<3;i++)
  {
    printf("ArrayWrite[%d]= %X ",i,ArrayWrite[i]);
  }
  	  printf("\r\n");

  for(int i=0;i<3;i++)
  {
    printf("ArrayRead[%d]= %X ",i,ArrayRead[i]);
  }
  	  printf("\r\n");

现象:

ArryRead[2]=FF,正好说明,不能跨页写入。可以跨页读

4.跨页写入代码测试
  for(int i=0;i<3;i++)
  {
    ArrayWrite[i]=i;
  }
  
  W25Q128_SectorErase(0x000000);
  HAL_Delay(10);
  W25Q128_SectorErase(0x000100);
  HAL_Delay(10);
  dev_flash_write_bytes_nocheck(ArrayWrite,0x0000FE,3);
//  W25Q128_PageProgram(0x0000FE,ArrayWrite,3);
  W25Q128_ReadData(0x0000FE,ArrayRead,3);
  for(int i=0;i<3;i++)
  {
    printf("ArrayWrite[%d]= %X ",i,ArrayWrite[i]);
  }
  	  printf("\r\n");

  for(int i=0;i<3;i++)
  {
    printf("ArrayRead[%d]= %X ",i,ArrayRead[i]);
  }
  	  printf("\r\n");

现象:

说明跨页写入代码没问题。

5.验证只能1改0,不能0改1
uint8_t ArrayWrite[3]={0x01,0x02,0x03};
uint8_t ArrayRead[3];

  W25Q128_SectorErase(0x000000);
  HAL_Delay(10);

  dev_flash_write_bytes_nocheck(ArrayWrite,0x0000FE,3);
//  W25Q128_PageProgram(0x0000FE,ArrayWrite,3);
  W25Q128_ReadData(0x0000FE,ArrayRead,3);
  for(int i=0;i<3;i++)
  {
    printf("ArrayWrite[%d]= %X ",i,ArrayWrite[i]);
  }
  	  printf("\r\n");

  for(int i=0;i<3;i++)
  {
    printf("ArrayRead[%d]= %X ",i,ArrayRead[i]);
  }
  	  printf("\r\n");

现象:

当我们不擦除页,改变数组时

uint8_t ArrayWrite[3]={0x02,0x03,0x04};
uint8_t ArrayRead[3];

//  W25Q128_SectorErase(0x000000);
//  HAL_Delay(10);
//  W25Q128_SectorErase(0x000100);
//  HAL_Delay(10);
  dev_flash_write_bytes_nocheck(ArrayWrite,0x0000FE,3);
//  W25Q128_PageProgram(0x0000FE,ArrayWrite,3);
  W25Q128_ReadData(0x0000FE,ArrayRead,3);
  for(int i=0;i<3;i++)
  {
    printf("ArrayWrite[%d]= %X ",i,ArrayWrite[i]);
  }
  	  printf("\r\n");

  for(int i=0;i<3;i++)
  {
    printf("ArrayRead[%d]= %X ",i,ArrayRead[i]);
  }
  	  printf("\r\n");

现象:

0x01&0x02=0,0x02&0x03=2,0x03&0x04=0.所以说明只能由1改0,不能0改1。

结束

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值