目录
SPI协议简介
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:数据有效性
3.CPOL/CPHA 及通讯模式
![](https://i-blog.csdnimg.cn/blog_migrate/30baacbd60c7236d01e9ba0535549088.jpeg)
模式 | CPOL | CPHA | SCK时钟(空闲时) | 采样时刻 |
0 | 0 | 0 | 低电平 | 奇数边沿 |
1 | 0 | 1 | 低电平 | 偶数边沿 |
2 | 1 | 0 | 高电平 | 奇数边沿 |
3 | 1 | 1 | 高电平 | 偶数边沿 |
我们通常使用”模式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;
}