SPI定义
SPI(Serial Peripheral Interface, 串口外设接口),它用于MCU与各种外围设备以串行形式进行通信,速度最高可达25MHz以上。
SPI接口次要利用在EEPROM、 FLASH、实时时钟、网络控制器、 OLED显示驱动器、 AD转换器,数字信号处理器、数字信号解码器等设施之间。
SPI通常由四条线组成,一条主设施输入与从设施输出( Master Output Slave Input, MOSI),一条主设施输出与从设施输入( Master Input Slave Output, MISO),一条时钟信号( Serial Clock, SCLK),一条从设施使能抉择( Chip Select, CS)。与I²C相似,协定比较简单,也能够应用GPIO模仿SPI时序。
SPI的数据交换
在SCLK时钟周期的驱动下, MOSI和MISO同时进行,如下图所示,能够看作一个虚构的环形拓扑构造。主机和从机都有一个移位寄存器,主机移位寄存器数据通过MOSI将数据写入从机的移位寄存器,此时从机移位寄存器的数据也通过MISO传给了主机,实现了两个移位寄存器的数据交换。无论主机还是从机,发送和接管都是同时进行的,如同一个“环”。 须要留神的是,数据的写入程序是从高地址到低地址。
如果主机只对从机进行写操作,主机只需疏忽接管的从机数据即可;如果主机要读取从机数据,须要主机发送一个空数据来引发从机发送数据。
传输模式
SPI有四种传输模式,次要差异在于CPOL和CPHA的不同。
CPOL( Clock Polarity,时钟极性),示意SCK在闲暇时为高电平还是低电平。 当CPOL=0,SCK闲暇时为低电平,当CPOL=1, SCK闲暇时为高电平。
CPHA( Clock Phase,时钟相位), 示意SCK在第几个时钟边缘采样数据。 当CPHA=0,在SCK第一个边际采样数据(即在第二个边际输入),当CPHA=1, 在SCK第二个边际采样数据(即在第一个边际输入数据)。
比方:CPHA=0时, 示意在时钟第一个时钟边际采样数据。 当CPOL=1,即闲暇时为高电平,从高电平变为低电平,第一个时钟边际(降落沿) 即进行采样。 当CPOL=0,即闲暇时为低电平,从低电平变为高电平,第一个时钟边际(回升沿)即进行采样。
数据传输流程
首先主机和从机都抉择同一传输模式。而后主机片选拉低,选中从机。接着在时钟的驱动下, MOSI发送数据,同时MISO读取接收数据。最初实现传输,勾销片选。
模仿SPI
/** 函数名: void SPI_WriteByte(uint8_t data)* 输出参数: data -> 要写的数据* 输入参数:无 * 返回值:无* 函数作用:模仿 SPI 写一个字节*/ void SPI_WriteByte(uint8_t data) { //SPI写1 Byte,循环8次,每次发送1 Bit; uint8_t i = 0; uint8_t temp = 0; for(i=0; i<8; i++) { temp = ((data&0x80)==0x80)? 1:0; //将data最高位保留到temp; data = data<<1; //data左移一位,将次高位变为最高位,用于下次取最高位; SPI_CLK(0); //CPOL=0 //拉低时钟,即闲暇时钟为低电平, CPOL=0; SPI_MOSI(temp); //依据temp值,设置MOSI引脚的电平; SPI_Delay(); //简略延时,能够定时器或延时函数实现 SPI_CLK(1); //CPHA=0 //拉高时钟, W25Q64只反对SPI模式0或1,即会在时钟回升沿采样MOSI数据; SPI_Delay(); } SPI_CLK(0); //最初SPI发送完后,拉低时钟,进入闲暇状态;}/** 函数名: uint8_t SPI_ReadByte(void)* 输出参数:* 输入参数:无* 返回值:读到的数据* 函数作用:模仿 SPI 读一个字节*/ uint8_t SPI_ReadByte(void) { //SPI读1 Byte,循环8次,每次接管1 Bit; uint8_t i = 0; uint8_t read_data = 0xFF; for(i=0; i<8; i++) { read_data = read_data << 1; //“凌空” read_data最低位,8次循环后,read_data将高位在前; SPI_CLK(0); //拉低时钟,即闲暇时钟为低电平; SPI_Delay(); SPI_CLK(1); SPI_Delay(); if(SPI_MISO()==1) { read_data = read_data + 1; } } SPI_CLK(0); //最初SPI读取完后,拉低时钟,进入闲暇状态 return read_data;}
后面提到SPI传输能够看作一个虚构的环形拓扑构造,即输出和输入同时进行。在后面“ SPI_WriteByte()”函数里,发送了1 Byte,也应该接管1 Byte,只是代码中疏忽了接管引脚MISO的状态; 在后面“ SPI_ReadByte()”函数里,接管了1 Byte,也应该发送1 Byte,只是代码中疏忽了发送引脚MOSI的内容。有些场景, SPI须要同时读写,因而还须要编写SPI同时读写函数。
/** 函数名: uint8_t SPI_WriteReadByte(uint8_t data)* 输出参数: data -> 要写的一个字节数据* 输入参数:无* 返回值:读到的数据* 函数作用:模仿 SPI 读写一个字节*/SPI读和写1 Byte,循环8次,每次发送和接管1 Bit; uint8_t SPI_WriteReadByte(uint8_t data) { uint8_t i = 0; uint8_t temp = 0; uint8_t read_data = 0xFF; for(i=0;i<8;i++) { temp = ((data&0x80)==0x80)? 1:0; //将data最高位保留到temp; data = data<<1; //data左移一位,将次高位变为最高位,用于下次取最高位; read_data = read_data<<1; //“凌空” read_data最低位,8次循环后,read_data将高位在前; SPI_CLK(0); SPI_MOSI(temp); SPI_Delay(); SPI_CLK(1); SPI_Delay(); if(SPI_MISO()==1) { //读取MISO上的数据,保留到以后read_data最低位,后续通过左移的形式腾挪地位; read_data = read_data + 1; } } SPI_CLK(0); return read_data;}