"); //-->
SPI的通信和IIC通信接口相似,都会两个模块间进行数据通信的一种方法。相比于IIC通信,SPI的通信原理很简单,一般主从方式工作,这种模式通常有一个主设备和一个或多个从设备,通常采用的是4根线,它们是MISO(数据输入,针对主机来说)、MOSI(数据输出,针对主机来说)、SCLK(时钟,主机产生)、CS/SS(片选,一般由主机发送或者直接使能,通常为低电平有效),典型的硬件连接如下:
在SPI通信中,时钟作为一项重要的参数,必须保证主设备和从设备必须在相同的时序下工作,因为SCK的时钟频率决定了整个SPI总线的传输速度,所以只有主从双方步调一致才能进行数据的稳定传输。在用MCU作为主设备时,一般可通过对SPI控制寄存器编程.来选择不同的时钟频率,如果采用单片机IO口模拟SPI时序,那么就需要严格按照从机的数据传输格式进行模拟。
CS/SS是片选信号,若为低电平有效,只有控制该管脚为低电平从机才能实现使能,利用单片机的IO口可控制总线上连接的多个SPI设备。
在SPI数据传输中,数据在时钟sclk的作用下按照bit进行传输的。这就是SCLK时钟线存在的原因,因此,完成一个字节(8bit)的数据传输,至少8次 时钟信号的改变(上沿和下沿为一次,依据不同的从机传送协议),就可以完成8位数据的传输。
在SPI操作中,最重要的两项设置就是时钟极性(CPOL或UCCKPL)和时钟相位(CPHA或UCCKPH)。为了保证主从机正确通信,应使得它们的SPI具有相同的时钟极性和时钟相位。
时钟极性(CPOL)定义了时钟空闲状态的电平,对数据传输的没有太大的影响。
时钟相位(CPHA)定义了数据的传送时间和传送格式。在程序编写时需要注意相位和数据的关系。下图形象的说明了时钟极性和数据传输的关系。
下面以STM32单片机为主机,SPI存储器作为从机进行程序的解释:
首先创建GPIO结构体:
GPIO_InitTypeDef GPIO_InitStructure;
因为SPI有MOSI MISO之分,所需的函数设置如下:
void MOSI_H(void)
{
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//MOSI 设置1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE, GPIO_Pin_12);
}
void MOSI_L(void)
{
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//MOSI 设置0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE, GPIO_Pin_12);
}
void SCLK_H(void)
{
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//scl 设置1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE, GPIO_Pin_13);
}
void SCLK_L(void)
{
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//MOSI 设置 0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE, GPIO_Pin_13);
}
针对SPI的初始化操作,具体如下:
void SPI_Init_IO(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//CS
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE,GPIO_Pin_13); //高电平,未片选
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //MISO
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//MOSI
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE,GPIO_Pin_12);
}
正如上面描述的,SPI是以字节的格式进行数据传输,因此需要8次循环完成8bit的数据传输。
void SpiFlash_WriteOneByte(u8 byte)
{
uint8_t BitCount = 0;
SCLK_L(); //clk低电平 存入数据,上升沿读入数据
for(BitCount = 0;BitCount < 8; BitCount++)
{
SCLK_L(); //clk低电平 存入数据,上升沿写入数据
if(byte & 0x80)
{
MOSI_H(); //MOSI为1,输写入1
}
else
{
MOSI_L();
}
byte <<= 1;
SCLK_H(); //时钟高电平,写入数据
}
SCLK_L();
MOSI_H(); //写入完成,MOSI保持高电平
}
读操作也与写操作相似,但是需要将读取到的数据返回,因此需要return1字节的数据。
u8 SpiFlash_ReadOneByte(void)
{
uint8_t BitCount = 0;
uint8_t retValue = 0;
SCLK_H(); //clk高电平 存入数据,下降读出数据
for(BitCount = 0;BitCount < 8; BitCount++)
{
retValue <<= 1;
SCLK_H(); //clk高电平
if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_10))
{
retValue |= 0x01;
}
else
{
retValue &= 0xFE;
}
SCLK_L(); //clk高电平// cs = 0 选中芯片
}
SCLK_H();
return (retValue);
}
上面的子函数为SPI所需的关键部分,将这些函数嵌入到主函数相关地方可实现SPI数据的读写操作。
正如我们所了解的,SPI具有接口简单,硬件容易实现;时钟速度快,没有系统开销;
相对抗干扰能力强;支持全双工操作等优点。但是,缺乏流控制机制,传输双方消息不确认,需要在软件上进行数据处理;没有多主机模式,如果需要设置为多主机,需要在软件或者外部逻辑上进行设计;需要占用主机较多的口线(每个从机都需要一根片选线)这些方面也需要注意。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。
eleaction01 阅读:2919