SPI : Serial Peripheral interface ,即串行外围设备接口。 SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,主要应用在 EEPROM(一种存储器),FLASH,实时时钟, AD 转换器,还有数字信号处理器和数字信号解码器之间。
SPI 主要特点: 可以同时发出和接收串行数据; 可以当作主机或从机工作; 提供频率可
编程时钟; 发送结束中断标志; 写冲突保护; 总线竞争保护等。
从图中可以看出, 主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器
写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机(从最高位开始,取代从机的最高位),从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机(从最低位开始,取代主机的最低位)。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
时钟极性(CPOL)和相位(CPHA)可以进行配置。
时钟极性(CPOL)对传输协议没有重大的影响。(决定上升沿采集还是下降沿采集)
CPOL=0,串行同步时钟的空闲状态为低电平。
CPOL=1,串行同步时钟的空闲状态为高电平。
时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
STM32 SPI接口可配置为支持SPI协议或者支持I2S音频协议,默认是SPI模式。可以通过软件切换到|2S方式。
Negative of Slave Select,也就是同步串行通讯的片选信号(低电平有效)
SS/CS(Slave Select/Chip Select):用于Master片选Slave,使被选中的Slave能够被Master访问;
NSS相当于主机的CS(自己理解的)
软件NSS模式: 可以通过设置SPI _CR1寄存器的SSM位来使能这种模式。在这种模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动
硬件NSS模式,分两种情况
-NSS输出被使能:当STM32 工作为主SPI,并且NSS输出已经通过SPI _CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从SPI设备。
此时,当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生-一个
硬件失败错误(Hard Fault)。
-NSS输出被关闭:允许操作于多主环境。
应用程序通过3个状态标志可以完全监控SPI总线的状态。
此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。当写入SPI _DR
时,TXE标志被清除。
此标志为’1’时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。
BSY标志由硬件设置与清除(写入此位无效果),此标志表明SPI通信层的状态。
1.配置相关引脚的复用功能,使能SPlx时钟
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
2.初始化SPIx,设置SPlx工作模式
SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)
3.SPI传输数据
SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)//发送数据
SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)//接收数据
4.查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)//类似上面的状态标志
typedef struct
{
uint16_t SPI_Direction;//通信方式, 半双工,全双工,以及串行发和串行收方式
uint16_t SPI_Mode;//主从模式
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;//时钟极性
uint16_t SPI_CPHA;//时钟相位
uint16_t SPI_NSS;//设置 NSS 信号由硬件(NSS 管脚)还是软件控制
uint16_t SPI_BaudRatePrescaler;//波特率预分频值
uint16_t SPI_FirstBit;//数据传输顺序是先MSB 位还是先LSB 位
uint16_t SPI_CRCPolynomial;// CRC 校验
}SPI_InitTypeDef;
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;//相关结构体初始化
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //工作模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据大小:8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //采样串行同步时钟的第二个跳变沿(上升或下降)数据
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS的软硬件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //波特率预分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输开始位
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure); //初始化SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
//SPIx 读写一个字节(SPI想要读必须写)
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
以W25Q128为例的读写数据
//pBuffer:数据存储区
//Read/writeAddr:开始读取的地址(24bit)
//NumByteToRead/write:要读取/写入的字节数(读取最大65535,写入不超过该页的剩余字节数)
void W25Q128_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
W25Q128_CS=0; //使能器件
SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令
SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送24bit地址
SPI2_ReadWriteByte((u8)((ReadAddr)>>8));
SPI2_ReadWriteByte((u8)ReadAddr);
for(i=0;i>16)); //发送24bit地址
SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
SPI2_ReadWriteByte((u8)WriteAddr);
for(i=0;i