STM32 IO模拟SPI
首先SPI通信有四种模式,由时钟相位(CPHA)和时钟极性(CPOL)的不同配置组合而成,当CPOL=0时,时钟空闲状态为低电平;当CPOL=1时,时钟空闲状态为高电平。当CPHA=0时,在时钟的第一个跳变沿(上升或下降)数据被采样;当CPHA=1时,在时钟的第二个跳变沿(上升或下降)数据被采样。主机和从机的配置应该是一致的。
下面两张图是从STM32F103的参考手册截取的,结合图便可以理解前面的内容。

图1

图2
以模式0(即CPOL=0,CPHA=0)为例,对应图2,看下主机端的代码该怎么写,此处忽略引脚初始化代码,仅讲解发送和接收的代码,先来看发送,代码如下:
/*software spi write byte*/
void SpiWriteByte_Software(uint8_t c)
{
//片选
SPI_NSS_L;
//MSB First
for(uint32_t i = 0;i < 8; i++)
{
/*主机准备数据*/
if(c & (uint8_t)0x80)
{
SPI_MOSI_H;
}
else
{
SPI_MOSI_L;
}
//获取下一个传输的bit
c <<= 0x1UL;
//拉高时钟,从机在上升沿读取主机数据
SPI_CLK_H;
//主机拉低时钟
SPI_CLK_L;
}
//去片选
SPI_NSS_H;
}
发送的时候我们只需要关心时钟信号、MOSI、NSS这三根线,在发送数据前先拉低片选(低有效),告诉从机准备好接收数据,主机将要发送的数据以高位先行的方式从MOSI线逐位移出,准备好第一个bit位后,拉高时钟线,从机在时钟的上升沿读取MOSI线的状态,读取后即得到了第一个bit位,拉低时钟线,主机准备第二个bit位,以此循环直到发送完所有数据。
理解了发送的过程,那么接收也就简单了,代码如下:
/*software spi read byte*/
uint8_t SpiReadByte_Software(void)
{
uint8_t retData = 0;
//片选
SPI_NSS_L;
for(uint32_t i = 0;i < 8; i++)
{
//拉高时钟,
SPI_CLK_H;
if(SPI_MISO)
{
retData |= ((uint8_t)0x80 >> i);
}
else
{
retData &= (~((uint8_t)0x80 >> i));
}
SPI_CLK_L;
}
//去片选
SPI_NSS_H;
return retData;
}
接收过程和发送过程不一样的就是把关心的MOSI换成了MISO而已,在接收数据前先拉低片选,告诉从机可以发送数据了,从机就会将bit数据准备好,主机拉高时钟,读取MISO的状态即可得到从机发送的第一个bit位,然后拉低时钟,告诉从机准备下一个传输的bit位,从机准备好后,主机再次拉高时钟,读取MISO状态,以此循环接收完所有数据。
上面的过程是发送和接收分离的,SPI协议是可以工作在全双工模式的,发送数据的同时也可以接收数据,只需要将发送和接收合并一下就行了。
将程序结合时序图,更加容易理解,当你理解了SPI协议,那么I2C和UART的通信模拟也是非常简单的,主要就是能看懂时序。





