STM32Cube学习篇(3)---SPI通讯实验(W25Q64)

目录

SPI协议简介

1.SPI物理层

2.SPI协议层

3.CPOL/CPHA 及通讯模式

4.SPI写入W25Q64实验


SPI协议简介

SPI 协议是由摩托罗拉公司提出的通讯协议 (Serial Peripheral Interface) ,即串行外围设备接口是一种高速全双工的通信总线。它被广泛地使用在 ADC LCD 等设备与 MCU 间,要求通讯速率 较高的场合。
下面是对SIP协议的物理层和协议层进行了讲解。

1.SPI物理层

SPI分别有3条总线和一天片选,分别位SCK(时钟总线)、MISO(主进从出)、MOSI(主出从进)、CS/NSS(片选),他们所特有的功能介绍如下:

  • SCK (Serial Clock) :时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯速率, 不同的设备支持的最高时钟频率不一样。
  • MISO(Master Input, Slave Output) :主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
  • MOSI (Master Output Slave Input) :主设备输出 / 从设备输入引脚。主机的数据从这条信号线输 出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
  • ( Slave Select) :从设备选择信号线,常称为片选信号线,也称为 NSS 、CS。当主机要选择从设备时,把该从设备的 NSS 信号线设,置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以 SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。

2.SPI协议层

SPI也是定义了通讯的起始和结束信号、数据有效性、始终同步等。

上图是有一个主机的通讯时序,其中MOSI、SLC、NSS是由主机控制产生,而 MISO 的信号由从机 产生,主机通过该信号线读取从机的数据。MOSI MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI MISO 传输一位数据。

标号1:通讯的起始和终止信号

NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 是每个从机 各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开 始准备与主机通讯。NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通 讯结束,从机的选中状态被取消。

标号2:数据有效性

SPI 使用 MOSI MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 MOSI MISO
据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。观察图中的标号处,MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。
在SCK的下降沿时刻,MOSI及MISO的数据有效,高电平位‘1’,低电平位‘0’。
注:SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

3.CPOL/CPHA 及通讯模式

SPI 一共有四种通讯模式, 它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。
时钟极性 CPOL : SPI 通讯设备处于空闲状态时(通讯前), SCK 信号线的电平信号。
CPOL=0 时, SCK 在空闲状态时为低电平, CPOL=1 时,则相反。
时钟相位 CPHA :是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。

我们来分析这个 CPHA=0 的时序图。首先,根据 SCK 在空闲状态时的电平,分为两种情况。 SCK 信号线在空闲状态为低电平时,CPOL=0 ;空闲状态为高电平时, CPOL=1
无论 CPOL=0 还是 =1 ,因为我们配置的时钟相位 CPHA=0 ,在图中可以看到,采样时刻都是在
SCK 的奇数边沿。注意当 CPOL=0 的时候,时钟的奇数边沿是上升沿,而 CPOL=1 的时候,时钟 的奇数边沿是下降沿。所以 SPI 的采样时刻不是由上升 / 下降沿决定的。 MOSI MISO 数据线的 有效信号在 SCK 的奇数边沿保持不变,数据信号将在 SCK 奇数边沿时被采样,在非采样时刻, MOSI 和 MISO 的有效信号才发生切换。
CPOL CPHA 的不同状态, SPI 分成了四种模式。当主机与从机工作模式相同时才能正常通讯。
模式CPOLCPHASCK时钟(空闲时)采样时刻
000低电平奇数边沿
101低电平偶数边沿
210高电平奇数边沿
311高电平偶数边沿

我们通常使用”模式0“与“模式3”

4.SPI写入W25Q64实验

软件环境和相关工具:

开发板:STM32F103

FLASH:W25Q64

IDE:STM32Cube MX、IAR

参考文档:W25Q64芯片手册、F103原理图

该实验通过串口调试和打印结果

1.先查看电路原理图,找到SPI1引脚,使用STM32Cube MX软件对SPI1进行配置

 配置系统时钟:

 SPI1基本配置:

 这里数据位选择8,因为W25Q64支持18M时钟频率,所以我们配置分频为4(72M /4 = 18M),工作 模式选用模式3.

2.基本配置完成之后,下一步就是对FLASH操作了。

首先需要阅读W25Q64芯片手册,读懂相关指令

W25Q64的操作原则:指令+地址+数据

部分指令集如下:

 接下来就是编写函数,单独写到bsp_spi.h、bsp_spi.c文件中,方便移植。

 编写相关函数:

/*
******************************************************************************
*       函 数 名:W25Qxx_ReadID
*       函数功能:读取W25Qxx外设ID
*       形    参:无
*       返 回 值:ID
*******************************************************************************
*/
uint16_t W25Qxx_ReadID(void)
{
  uint16_t id;
  uint8_t Txbuff[4] ={READ_ID_CMD,0,0,0}; 
  SPI1_CS_LOW;
  HAL_SPI_Transmit(&hspi1,(uint8_t *)Txbuff,4,1000);
  HAL_SPI_Receive(&hspi1,(uint8_t *)&id,2,1000);  
  SPI1_CS_HIGH;
  
  return id;
}
static uint8_t W25Qxx_GetStatus(void)
{
   uint8_t result;
   uint8_t Txbuff[4] ={READ_STATUS_REG1_CMD,0,0,0};
   
   SPI1_CS_LOW;
   if(HAL_OK == HAL_SPI_Transmit(&hspi1,(uint8_t *)Txbuff,1,1000))
   {
      if(HAL_OK == HAL_SPI_Receive(&hspi1,&result,1,1000))
        return result;
   }
   SPI1_CS_HIGH;
   
   return 0;
}

/*
******************************************************************************
*       函 数 名:W25Qxx_Wait_Busy
*       函数功能:等待W25xx驱动
*       形    参:无
*       返 回 值:无
*******************************************************************************
*/
void W25Qxx_Wait_Busy(void)
{
  while(W25Qxx_GetStatus() & 0x01 == 0x01);
}
/*
******************************************************************************
*       函 数 名:W25Qxx_Write_Enable、W25Qxx_Write_Disable
*       函数功能:写命令使能、失能
*       形    参:无
*       返 回 值:无
*******************************************************************************
*/
void W25Qxx_Write_Enable(void)
{
  uint8_t TxBuff[1] ={WRITE_ENABLE_CMD};
  SPI1_CS_LOW;
  HAL_SPI_Transmit(&hspi1,TxBuff,1,1000);
  SPI1_CS_HIGH;
  
  W25Qxx_Wait_Busy();                   //等待W25Qxx空闲
}
void W25Qxx_Write_Disable(void)
{
  uint8_t TxBuff[1] ={WRITE_DISABLE_CMD};
  SPI1_CS_LOW;
  HAL_SPI_Transmit(&hspi1,TxBuff,1,1000);
  SPI1_CS_HIGH;
  
  W25Qxx_Wait_Busy();                   //等待W25Qxx空闲
}
/*
******************************************************************************
*       函 数 BSP_W25Qx_Write
*       函数功能:写入数据
*       形    参:pData:接收数据、WriteAddr:写入地址、Size:写入字节数
*       返 回 值:0:成功;-1:error
*******************************************************************************
*/
uint8_t BSP_W25Qx_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
{
  uint8_t cmd[4];
  cmd[0] =PAGE_PROG_CMD;
  cmd[1] =(uint8_t)(WriteAddr >>16);
  cmd[2] =(uint8_t)(WriteAddr >>8);
  cmd[3] =(uint8_t)WriteAddr;
  
  W25Qxx_Write_Enable();
  SPI1_CS_LOW;
  HAL_SPI_Transmit(&hspi1,(uint8_t *)cmd,4,1000);
  if(HAL_OK != HAL_SPI_Receive(&hspi1,pData,Size,1000))
    return -1;
  
  SPI1_CS_HIGH;
  
  W25Qxx_Wait_Busy();
  return 0;
}
/*
******************************************************************************
*       函 数 名:BSP_W25Qx_Read
*       函数功能:读取数据
*       形    参:pData:接收数据、ReadAddr:读取地址、Size:读取字节数
*       返 回 值:0:成功;-1:error
*******************************************************************************
*/
uint8_t BSP_W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
{
  uint8_t cmd[4];
  cmd[0] =READ_CMD;
  cmd[1] =(uint8_t)(ReadAddr >>16);
  cmd[2] =(uint8_t)(ReadAddr >>8);
  cmd[3] =(uint8_t)ReadAddr;
  W25Qxx_Wait_Busy();
  
  SPI1_CS_LOW;
  HAL_SPI_Transmit(&hspi1,(uint8_t *)cmd,4,1000);
  if(HAL_OK != HAL_SPI_Receive(&hspi1,pData,Size,1000))
    return -1;
  
  SPI1_CS_HIGH;
  return 0;
}
/*
*******************************************************************************
*       函    数: Erase_Sector
*       函数功能: 擦除指定扇区
*       形    参:sector_addr:扇区地址
*       返 回 值:0:成功;-1:error
*******************************************************************************
*/
uint8_t Erase_Sector(uint32_t sector_addr)
{
  uint8_t cmd[4];
  sector_addr *= 4096;
  cmd[0] =SECTOR_ERASE_CMD;
  cmd[1] =(uint8_t)(sector_addr >>16);
  cmd[2] =(uint8_t)(sector_addr >>8);
  cmd[3] =(uint8_t)sector_addr;
  
  W25Qxx_Write_Enable();
  W25Qxx_Wait_Busy();
  SPI1_CS_LOW;
  if(HAL_OK != HAL_SPI_Transmit(&hspi1,cmd,4,1000))
    return -1;
  SPI1_CS_HIGH;
  W25Qxx_Wait_Busy();
  
  return 0;

最后就是调动函数进行验证、通过串口打印信息

/*
*************************************************************************************
*       函 数 名:W25xx_Demo
*       函数功能:W25驱动测试
*       形    参:无
*       返 回 值:无
*************************************************************************************
*/
static void W25xx_Demo(void)
{
  uint8_t Txbuff[5] ={0x01,0x02,0x03,0x04,0x05};
  uint8_t Rxbuff[5] ={0,0,0,0,0};
  uint16_t device_id;
  device_id =W25Qxx_ReadID();
  printf("device_id =%d\r\n",device_id);
  
  Erase_Sector(0);
  BSP_W25Qx_Write(Txbuff,0x00,5);
  BSP_W25Qx_Read(Rxbuff,0x00,5);
  
  for(uint8_t i =0;i <5;i++)
    printf("Rxbuff[%d] =%d\r\n",i,Rxbuff[i]);
}


int main()
{
    W25xx_Demoo();

    return 0;
}

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值