目录
一、SPI简介
1、全双工与半双工
2、同步与异步
3、SPI通信方式
二、SPI工作模式
三、W25Q128BV
1、读ID Read Manufacturer/Device ID(90h)
2、读ID代码实现(硬件SPI)
3、IO口模拟SPI时序图实现 (软件SPI) 模式3
一、SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高速的(10Mbps),全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,如NRF24L01、VS1053、SD卡等。
1、全双工与半双工
a:半双工常见于485总线,即下图所示,使用的是差分信号,这样子就决定了是半双工,即任何一个传输时刻,只有一个传输方向,要么是发送,要么是接收。优点在于传输距离远,理论上达到1.2km。
d:全双工常见SPI通信,即下图所示,两边可以同时收发数据,优点是速度快,延迟小。
2、同步与异步
a:同步的典型特征:有时钟线,可以控制通信的速度。
b:异步的典型特征:约束好通信的波特率,数据帧格式.
3、SPI通信方式
根据上图我们可以看到,由于SPI是全双工,同步的通信总线,SCLK,MOSI,MISO这三个引脚可以共用于从机,根据片选引脚(/SS)来区别来区别从机(输出低电平的从机有效工作),注意:硬件连接看起来是一对多,一个主机对应多个从机,但是实际上通信只能一对一通信,当一个硬件处于通信状态(输出低电平),其他硬件处于等待状态(输出高电平),不然容易出现数据的冲突。
SCLK(Serial Clock):串行时钟线
MOSI(Master Output Slave Input):主机输出数据,从机接收数据
MISO(Master Input Slave Output):从机输出数据,主机接收数据
/SS(slave select):从机选择,该引脚输出低电平,该引脚有效工作
二、SPI工作模式
SPI总线有四种工作模式,其中使用最为广泛的是模式0和模式3方式。
CPOL(Clock Polarity):时钟极性选择,为0时且SPI总线空闲时,时钟线为低电平 ; 为1时且SPI总线空闲时,时钟线为高电平。
CPHA(Clock Phase):时钟相位选择,为0时在SCLK第一个跳变沿,主机对MISO引脚电平采样;为1时在SCLK第二个跳变沿,主机对MISO引脚电平采样 。
通过上面两张图我们可以发现,模式0是从机先发送第一个数据,模式3是主机先发送第一个数据,它们之间有什么区别呢,其实没有什么区别,作用就是做好通信前的准备,约定好数据的采集时机。
三、W25Q128BV
W25Q128BV:内存128MBit/16MType,每个可编程页256字节,支持SPI模式0和模式3。具体可以看相关手册。由于手册繁多且复杂,这边就不多做描述。
关于SPI SLASH具体描述可参考博主:(15条消息) W25Q128数据手册阅读总结_百里之外的博客-CSDN博客https://blog.csdn.net/qq_40993639/article/details/122053404
1、读ID Read Manufacturer/Device ID(90h)
本文我们讲一讲常用的指令中的一种,读ID Read Manufacturer/Device ID(90h),下图是完整的时序图,数据手册是分两个图来看,我这里合成一个图看着更方便一点。关于这个时序图的工作方式在手册有总结,是一段英文解释,我这边用自己的话解释这个时序图的工作原理。
在这个指令传输之前,我们要使/cs(/ss)引脚拉低,移位传输指令码90h(1001 0000)对应时钟线(CLK)0-7,每一个时钟周期对应一个Bit(比特),紧接着我们要传输24-BIt(比特)地址(A23-A0),其对应时钟线(CLK)8-31,也就是3个字节地址,完成上诉工作之后,厂商ID和设备ID就会发给我们主机,通信结束后,使/cs(/ss)引脚拉高。
通过读ID往往能够检测到SPI通信是否正常,也能够知道从机设备是否在线。
2、读ID代码实现(硬件SPI)
编程环境:keil5 STM32f407
首先初始化w25q128。
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
void w25q128_init(void)
{
//使能端口B的硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//使能SPI1的硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
//配置PB3~PB5为复用功能模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; //指定3、4、5号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//配置为复用功能模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
//将PB3~PB5连接到SPI1的硬件
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
//配置PB14为输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //指定14号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//配置为推挽功能模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PB14初始电平状态为高电平,因为总线空闲的时候,SS引脚(片选引脚)为高电平
PBout(14)=1;
//SPI1参数的配置 //硬件SPI设置 模拟SPI不需要这个则删除掉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//每次传输最小单元为字节
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//根据从机的手册来配置,模式3
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//根据从机的手册来配置,模式3,第二跳变沿
//由软件代码控制片选引脚 SPI_NSS_Hard表示由硬件控制引脚
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
//波特率 根据从机的手册来配置,SPI的硬件时钟=84MHz/16=5.25MHz
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
//根据从机的手册来配置 MSB表示最高有效位传输
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
//使能SPI1硬件工作
SPI_Cmd(SPI1,ENABLE);
}
读ID
//官方代码,
uint8_t SPI1_SendByte(uint8_t byte)
{
/*!< Loop while DR register in not emplty */
//检测是否发送完毕
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/*!< Send byte through the SPI1 peripheral */
//如果发送完,就把数据发送出去
SPI_I2S_SendData(SPI1, byte);
/*!< Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/*!< Return the byte read from the SPI bus */
//对方返回一个字节回来
return SPI_I2S_ReceiveData(SPI1);
}
void w25q128_read_id(uint8_t *m_id,uint8_t *d_id)
{
W25Q128_SS=0;
SPI1_SendByte(0x90);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
//这里参数随便填,从机会忽略引脚电平状态
*m_id=SPI1_SendByte(0xFF);
*d_id=SPI1_SendByte(0xFF);
W25Q128_SS=1; //拉高结束
}
获取厂商ID和设备ID并打印
int main()
{
uint8_t m_id,d_id;
//串口初始化
usart1_init(115200);
w25q128_init();
w25q128_read_id(m_id,d_id);
printf("m_id=%X,d_id=%x\r\n",m_id,d_id);
while(1)
{
}
}
打印结果
从0地址连续读64字节的数据实现 Read Data(03H)
基本上不变,指令变为03H,后面就是传输连续字节,接收完一个字节,其指向会指向新的地址,每个地址都指向有效数据(会自动偏移)。
//通用写法
void w25q128_read(uint32_t addr,uint8_t *buf,uint32_t len)
{
uint8_t *p = buf;
//拉低开始
W25Q128_SS=0;
SPI1_SendByte(0x03);
//假如有个地址为0x123456.
SPI1_SendByte(addr>>16); //0x12
SPI1_SendByte(addr>>8); //0x34
SPI1_SendByte(addr); //0x56
while(len--)
{
*p=SPI1_SendByte(0xFF);
p++;
}
//拉高结束
W25Q128_SS=1;
}
打印
3、IO口模拟SPI时序图实现 (软件SPI) 模式3
初始化模拟SPI w25q128
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
#define W25Q128_SS PBout(14)
#define W25Q128_SCLK PBout(3)
#define W25Q128_MOSI PBout(5)
#define W25Q128_MISO PBin(4)
void w25q128_init(void)
{
//使能端口B的硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//配置PB3 PB5 PB14为输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_5|GPIO_Pin_14; //指定3、5、14号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//配置为输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置PB4为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //指定4号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//配置为输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度越高,功耗就越高,但是响应速度也更快
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//不需要使能内部上下拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PB14初始电平状态为高电平,因为总线空闲的时候,SS引脚(片选引脚)为高电平
W25Q128_SS=1;
//CPOL=1,SCLK引脚在SPI总线空闲的时候为高电平
W25Q128_SCLK=1;
//MOSI引脚随意电平 可高可低
W25Q128_MOSI=1;
}
读ID,只不过发送需要自己写
uint8_t SPI1_SendByte(uint8_t byte)
{
int32_t i=0;
uint8_t d=0;
//一个周期的变化
for(i=7; i>=0; i--)
{
if(byte & (1<<i))
W25Q128_MOSI=1;
else
W25Q128_MOSI=0;
W25Q128_SCLK=0; //模式3单个周期 SCLK拉低
delay_us(1); //延时1微秒
W25Q128_SCLK=1; //模式3单个周期 SCLK拉高
delay_us(1); //延时1微秒
//判断MISO是否是高电平
if(W25Q128_MISO)
d|=1<<i;
}
return d;
}
void w25q128_read_id(uint8_t *m_id,uint8_t *d_id)
{
W25Q128_SS=0;
SPI1_SendByte(0x90);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
*m_id=SPI1_SendByte(0xFF);
*d_id=SPI1_SendByte(0xFF);
W25Q128_SS=1;
}
成功打印数据,与硬件SPI打印是一样的 文章来源:https://www.toymoban.com/news/detail-633191.html
本次就分享到这里,如看到我理解错的地方也请指正我,感谢各位的观看❤文章来源地址https://www.toymoban.com/news/detail-633191.html
到了这里,关于SPI FLASH(W25Q128BV) 包含SPI工作原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!