SPI通信读取W25Q64

简介: 参考自野火STM32教程

SPI通信协议

主机与从机之间主要通过四根线连接:

片选线:NSSCS,主机选择从机时,把该信号线置低电平开始,置高电平结束通信

SCK:时钟信号线,由主机产生,决定通讯速率

MOSI:主设备输出/从设备输入引脚,该线上的数据方向为主机到从机

MISO:主输入/从输出引脚,数据方向为从机到主机

image.png

SPI通信时序

只有MISO信号是从机产生,其余都是主机产生,MOSIMISO数据的输入输出是同时进行的,使用SCK进行同步

一般采用图中MSB先行(高位先行)的模式 每次传输单位数不限制,816位均可

image.png

起始信号:从机的片选线被置为低电平,主机与该从机通信

传输信号:在时钟信号线上升沿时,主机与从机将数据放在信号线上,置01

信号收集:在时钟信号线下降沿时,主机与从机采集收到的信号

结束信号:片选线被置为高电平,通信结束


CPOL/CPHA及通讯模式

SPI共有四种通讯模式,区别在于总线空闲时SCK的时钟状态及数据采样时刻

CPOL:时钟极性,SPI开始通信前NSS线为高电平时SCK的状态,CPOL=0时,SCK为低电平,CPOL=1时,SCK为高电平

CPHA:时钟相位:指数据采样的时刻,CPHA=0时,数据的信号在SCK的奇数边沿被采样,CPHA=1时,在SCK的偶数边沿被采样

下图为奇数边沿采样的时序图,CPOL=0 的时候,时钟的奇数边沿是上升沿,而 CPOL=1 的时候,时钟的奇数边沿是下降沿

image.png

下图为偶数边沿采样的时序图

image.png

主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是模式03,都是SCK上升沿时被采样

image.png


SPI架构

image.png

SPI1位于APB2上,最高速率36M,其余在APB1上,最高18M

image.png

SCK时钟分频(CR寄存器中的BR位)fpclkAPB1/2总线的频率

image.png

通过写 SPI 数据寄存器 DR”把数 据填充到发送缓冲区中,通讯读数据寄存器 DR”,可以获取接收缓冲区中的内容。

其中数据帧 长度可以通过控制寄存器 CR1”“DFF 配置成 8 位及 16 位模式;

配置“LSBFIRST 可选择 MSB 先行还是 LSB 先行。

我们一般不使用SPI 外设的标准 NSS 信号线,而是使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号


主机端收发数据时序:

image.png

若使能了 TXE RXNE 中断,TXE RXNE 1 时会产生 SPI 中断信号,在中断中进行判断和处理,也可以使用DMA方式来收发

image.png

SPI初始化结构体:

image.png

W25Qxx.c

//PA4----SPI1_NSS
//PA5----SPI1_SCK
//PA6----SPI1_MISO
//PA7----SPI1_MOSI
/************************************************************
 *函数功能:初始化配置引脚及SPI功能结构体
 *参数:
 *返回值:
**************************************************************/
void My_SPI2_Init()
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOA,&GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOA,&GPIO_InitStructure);
  SPI_CS_High();
  SPI_InitTypeDef SPI_InitStructure;
  SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;//1  这里是模式3
  SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;//1
  SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;//2分频
  SPI_InitStructure.SPI_CRCPolynomial=7;
  SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//数据帧8位
  SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工模式
  SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;//高位先行模式
  SPI_InitStructure.SPI_Mode=SPI_Mode_Master;//主机模式
  SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//软件控制片选
  SPI_Init(SPI1,&SPI_InitStructure);
  SPI_Cmd(SPI1,ENABLE);
}
/************************************************************
 *函数功能:发送一个字节
 *参数:要发送的字节
 *返回值:接收到的字节
**************************************************************/
uint8_t SPI_SendByte(uint8_t Byte)
{
  uint32_t i=1000;
  while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET)//发送空标志位置1
  {
    if((i--)==0){
    Serial_Printf("\r\n send error\r\n");
    return 0;
    }
  }
  SPI_I2S_SendData(SPI1,Byte);
  i=1000;
  while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET)//接收缓冲区非空置1
  {
    if((i--)==0){
    Serial_Printf("\r\n receive error\r\n");
    return 0;
    }
  }
  SPI_I2S_ReceiveData(SPI1);
}
/************************************************************
 *函数功能:接收数据
 *参数:
 *返回值:接收到的数据
**************************************************************/
uint8_t SPI_ReadByte()
{
  return (SPI_SendByte(DafutVal));
}
/************************************************************
 *函数功能:读取flash id
 *参数:
 *返回值:
**************************************************************/
uint32_t  SPI_ReadFlashID()
{
  uint32_t temp1=0,temp2=0,temp3=0,ID=0;
  SPI_CS_Low();
  SPI_SendByte(W25X_JedecDeviceID);//发送读取ID指令
  temp1=SPI_ReadByte();
  temp2=SPI_ReadByte();
  temp3=SPI_ReadByte();
  SPI_CS_High();
  ID=(temp1<<16)|(temp2<<8)|temp3;
  return ID;
}
/************************************************************
 *函数功能:芯片写使能
 *参数:
 *返回值:
**************************************************************/
void SPI_W25QXX_WriteEnable()
{
  SPI_CS_Low();
  SPI_SendByte(W25X_WriteEnable);
  SPI_CS_High();
}
/************************************************************
 *函数功能:等待上一次写操作完成
 *参数:
 *返回值:
**************************************************************/
void SPI_W25Q64XX_WaitWriteEnd()
{
  uint8_t Status=0;
  SPI_CS_Low();
  SPI_SendByte(W25X_ReadStatusReg);
  Status=SPI_ReadByte();
  while((Status&0x01)==SET)//最低位判断忙碌位
  {
    Status=SPI_ReadByte();
  }
  SPI_CS_High();
}
/************************************************************
 *函数功能:擦除FLSH指定扇区
 *参数:扇区地址
 *返回值:
**************************************************************/
void SPI_Erase_Sector(uint32_t addr)
{
  SPI_W25QXX_WriteEnable();
  SPI_W25Q64XX_WaitWriteEnd();
  SPI_CS_Low();
  SPI_SendByte(W25X_SectorErase);//发送擦除扇区指令
  SPI_SendByte((addr>>16)&0xFF);//高位地址先发送
  SPI_SendByte((addr>>8)&0xFF);
  SPI_SendByte(addr&0xFF);
  SPI_CS_High();
  SPI_W25Q64XX_WaitWriteEnd();
}
/************************************************************
 *函数功能:读取FLSH内容
 *参数:地址,缓存数组,读取个数
 *返回值:
**************************************************************/
void SPI_Read_Data(uint32_t addr,uint8_t *receiveBuff,uint32_t length)
{
  SPI_CS_Low();
  SPI_SendByte(W25X_ReadData);//发送读取指令
  SPI_SendByte((addr>>16)&0xFF);//发送读取地址
  SPI_SendByte((addr>>8)&0xFF);
  SPI_SendByte(addr&0xFF);
  while(length--)
  {
    *receiveBuff=SPI_ReadByte();
    receiveBuff++;
  }
  SPI_CS_High();
}
/************************************************************
 *函数功能:写入FLSH内容   
 *参数:地址,写入数组,写入个数(最多支持写入256个字节)
 *返回值:
**************************************************************/
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t length)
{
  SPI_W25QXX_WriteEnable();
  SPI_CS_Low();
  SPI_SendByte(W25X_PageProgram);//发送读取指令
  SPI_SendByte((addr>>16)&0xFF);//发送读取地址
  SPI_SendByte((addr>>8)&0xFF);
  SPI_SendByte(addr&0xFF);
  while(length--)
  {
    SPI_SendByte(*writeBuff);
    writeBuff++;
  }
  SPI_CS_High();
  SPI_W25Q64XX_WaitWriteEnd();
}
/************************************************************
 *函数功能:读取Flash 的Deveice ID
 *参数:
 *返回值:
**************************************************************/
uint32_t SPI_ReadDeviceID()
{
  uint32_t temp=0;
  SPI_CS_Low();
  SPI_SendByte(W25X_DeviceID);
  SPI_ReadByte();
  SPI_ReadByte();
  SPI_ReadByte();
  temp=SPI_ReadByte();
  SPI_CS_High();
  return temp;
}
/************************************************************
 *函数功能:进入掉电模式
 *参数:
 *返回值:
**************************************************************/
void SPI_W25QXX_PowerDown()
{
  SPI_CS_Low();
  SPI_SendByte(W25X_PowerDown);
  SPI_CS_High();
}
/************************************************************
 *函数功能:唤醒
 *参数:
 *返回值:
**************************************************************/
void SPI_W25QXX_WakeUP()
{
  SPI_CS_Low();
  SPI_SendByte(W25X_ReleasePowerDown);
  SPI_CS_High();
}

W25Qxx.h

#ifndef __W25QXX_H_
#define __W25QXX_H_
#define W25X_WriteEnable      0x06 
#define W25X_WriteDisable     0x04 
#define W25X_ReadStatusReg    0x05 
#define W25X_WriteStatusReg   0x01 
#define W25X_ReadData         0x03 
#define W25X_FastReadData     0x0B 
#define W25X_FastReadDual     0x3B 
#define W25X_PageProgram      0x02//写入 
#define W25X_BlockErase       0xD8 
#define W25X_SectorErase      0x20 
#define W25X_ChipErase        0xC7 
#define W25X_PowerDown        0xB9 
#define W25X_ReleasePowerDown 0xAB 
#define W25X_DeviceID         0xAB 
#define W25X_ManufactDeviceID 0x90 
#define W25X_JedecDeviceID    0x9F
#define DafutVal0x00
#define SPI_CS_High()GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define SPI_CS_Low()GPIO_ResetBits(GPIOA,GPIO_Pin_4)
void My_SPI2_Init(void);
uint8_t SPI_SendByte(uint8_t Byte);
uint8_t SPI_ReadByte(void);
void SPI_W25QXX_PowerDown(void);
void SPI_W25QXX_WakeUP(void);
uint32_t  SPI_ReadFlashID(void);
uint32_t SPI_ReadDeviceID(void);
void SPI_Erase_Sector(uint32_t addr);
void SPI_W25Q64XX_WaitWriteEnd(void);
void SPI_Read_Data(uint32_t addr,uint8_t *receiveBuff,uint32_t length);
void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t length);
#endif

main.c

uint8_t ReadBuf[4096];//扇区擦除是擦除4096个字节
uint8_t WriteBuf[256];
int main(void)
{
  uint32_t ID;
  uint16_t i;
  OLED_Init();
  Serial_Init();
  My_SPI2_Init();
  Serial_Printf("\r\n SPI_Flash测试\r\n");
  //读取ID
  ID=SPI_ReadFlashID();
  Serial_Printf("\r\n FLASH ID:0x%x",ID);
  OLED_ShowHexNum(1,1,ID,6);
  //从0地址开始扇区擦除
  SPI_Erase_Sector(0);
  //从0地址写入测试
  for(i=0;i<25;i++)
  {
    WriteBuf[i]=i;
  }
  SPI_Write_Data(0,WriteBuf,25);
  //从0地址读取测试
  SPI_Read_Data(0,ReadBuf,4096);//读取擦除后的扇区
  for(i=0;i<4096;i++)
  {
    Serial_Printf("0x%x ",ReadBuf[i]);
    if(i%10==0)
    {
    Serial_Printf("\r\n");
    }
  }
  while(1)
  {
  }
}


目录
相关文章
|
运维 监控
浅析SPI与CAN通信
SPI是一种常用的MCU与外设的通信方式,英文全称Serial Peripheral Interface。与之前介绍过的UART不同,SPI是串行,全双工,同步通信方式。SPI通常有4根物理连接线,分别是CS片选,SCK时钟,MOSI主机输出从机输入和MISO主机输入从机输出。CS片选是从机选择信号线,低电平有效。当CS为低电平时认为主机目前选中的本从机。SCK是串行时钟线,同步通信需要主从机时钟同步,主机利用SCK线与从机实现时钟同步。时钟由主机产生,决定了通讯的速率。
294 0
|
2月前
【通信协议讲解】单片机基础重点通信协议解析与总结之SPI(二)
【通信协议讲解】单片机基础重点通信协议解析与总结之SPI(二)
|
4月前
在Modbus RTU中同时进行读取和写入操作
在Modbus RTU中同时进行读取和写入操作
85 0
|
7月前
STM32--SPI通信与W25Q64(2)
STM32--SPI通信与W25Q64(2)
104 0
|
7月前
|
存储 传感器 芯片
STM32--SPI通信与W25Q64(1)
STM32--SPI通信与W25Q64(1)
218 0
|
Dubbo Java 机器人
聊聊 SPI 机制
SPI 的本质是将**接口实现类**的**全限定名配置在文件**中,并由**服务加载器读取配置文件,加载实现类**。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
|
7月前
|
网络协议 网络安全
SPI 机制
SPI 机制
125 0
|
监控 Scala 虚拟化
深入理解AMBA总线(十三)AXI原子访问机制和AXI响应
深入理解AMBA总线(十三)AXI原子访问机制和AXI响应
784 0