目录
1.SPI通信简介
1.1 主从模式
1.2 4根通信线
1.3 数据传输
2. SPI总线时序及其4种工作模式
2.1 SPI数据移位示意
2.2 SPI四种工作模式
2.2.1 mode 0 :CPOL = 0,CPHA=0
2.2.2 mode 1: CPOL = 0,CPHA = 1
2.2.3 mode 2: CPOL = 1,CPHA = 0
2.2.4 mode 3: CPOL = 1,CPHA = 1
3. SPI 模式0 代码编写
1.SPI通信简介
SPI(Serial Peripheral Interface)通信是一个全双工、同步,可以一主多从的一种串行通信协议,该通信模式需要4根线,分别是MISO\MOSI\CLK\CS,广泛用于微控制器与各种外围设备(如传感器、存储器、显示器等)之间的数据交换。
1.1 主从模式
SPI采用主从模式(Master-Slave)。在通信过程中,通常有一个主设备(Master)和一个或多个从设备(Slave)。主设备控制整个通信过程。
1.2 4根通信线
MISO(Master input Slave Output):主设备接收数据,从设备发送数据。一般主设备的MISO引脚连接到从设备的MOSI引脚。
MOSI(Master output Slave Input):主设备发送数据,从设备接收数据。主设备的MOSI引脚连接到从设备的MISO引脚
SCK(Serial Clock):同步时钟线,由主设备生成,用于同步数据传输。主设备的SCK引脚连接到从设备的SCK引脚。
CS/SS(Chip Select/ Slave Select):从设备选择信号,由主设备控制,低电平有效。在单主多从的模式下,主设备通过控制相应从机的CS引脚来选择和哪个从设备进行通信。只有被主设备选择的从设备才能相应从设备的指令。
一主多从的接线图如下所示:
在和STM32 进行接线的时候,输出引脚需要配置为推挽输出模式,输入引脚需要配置为浮空或者上拉输入。
1.3 数据传输
SPI总线在传输设备时,先传送高位,后传送低位;数据线为高电平表示逻辑1,低电平表示逻辑0,主设备/从设备在时钟线的上升沿或者下降沿往数据线上发送数据,or读取数据。
SPI为全双工通信,全双工通信即可以同时发送和接收数据。
2. SPI总线时序及其4种工作模式
2.1 SPI数据移位示意
SPI数据是如何在主机和从机之间传输的?下图展示了前两位数据移动的过程,主机波特率发生器生成时钟信号,通过SCK将时钟信号同步给从机。SPI是先传送高位,后传送低位,因此会将数据左移,每来一个时钟信号,完成一位数据传输。
对于SPI时序和数据发送不太清楚的,强烈推荐观看B站UP主江协科技的STM32SPI章节。传送门:江协科技-SPI讲解
2.2 SPI四种工作模式
主设备会通过波特率发生器来产生相应的时钟脉冲,时钟脉冲组成了时钟信号,时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制着两个SPI设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输的。
- CPOL = 1:表示空闲时是高电平
- CPOL = 0:表示空闲时是低电平
- CPHA = 1:表示从第一个跳变沿开始采样
- CPHA = 0:表示从第二个跳变沿开始采样
SPI通过时钟极性(CPOL)和时钟相位(CPHA)的搭配来得到四种工作模式。
2.2.1 mode 0 :CPOL = 0,CPHA=0
模式0的特性为:
CPOL = 0:空闲时是SCK低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 0:数据在第1个跳变沿(上升沿)采样(SCK第一个边沿移入数据,第二个边沿移出数据)
mode 0模式下SCK低电平表示空闲,可以看到SS从高电平拉达到低电平,表示主机选择该从机,MISO也从高阻状态变换到高电平(或者低电平,由具体数据决定)。
模式0表示SCK第一个跳变沿进行采样,第二个边沿即表示一个时钟周期,完成数据移位。
2.2.2 mode 1: CPOL = 0,CPHA = 1
模式1的特性为
CPOL = 0:空闲时是SCK低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 1:数据在第2个跳变沿(上升沿)采样(SCK第一个边沿移出数据,第二个边沿移入数据)
mode 1 模式下,SCK低电平国表示空闲,在第一个时钟跳变沿沿到来时,主从两机已经做好准备,并在第二个跳变沿进行数据传输。
2.2.3 mode 2: CPOL = 1,CPHA = 0
模式2的特性为:
CPOL = 1:空闲时SCK为高电平
CPHA = 0:第一个时钟跳变沿进行采样
模式2状态下,SCK高电平为空闲,第一个跳变沿进行数据传输。
2.2.4 mode 3: CPOL = 1,CPHA = 1
模式3的特性为:
CPOL = 1:空闲时SCK高电平
CPHA = 1:第二个时钟跳变沿进行采样
模式3状态下,SCK高电平为空闲,第二个跳变沿进行数据传输。
3. SPI 模式0 代码编写
代码来自B站up主:江协科技
//spi_.c
#include "stm32f10x.h" // Device header
/*引脚配置层*/
/**
* 函 数:SPI写SS引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
*/
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
/**
* 函 数:SPI写SCK引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平
*/
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平
}
/**
* 函 数:SPI写MOSI引脚电平
* 参 数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平
*/
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}
/**
* 函 数:I2C读MISO引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
*/
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回
}
/**
* 函 数:SPI初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
* CS——PA4
* DO——PA6 从机输出
* CLK—PA5
* DI——PA7 从机输入
*/
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4、PA5和PA7引脚初始化为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*设置默认电平*/
MySPI_W_SS(1); //SS默认高电平
MySPI_W_SCK(0); //SCK默认低电平
}
/*协议层*/
/**
* 函 数:SPI起始
* 参 数:无
* 返 回 值:无
*/
void MySPI_Start(void)
{
MySPI_W_SS(0); //拉低SS,开始时序
}
/**
* 函 数:SPI终止
* 参 数:无
* 返 回 值:无
*/
void MySPI_Stop(void)
{
MySPI_W_SS(1); //拉高SS,终止时序
}
/**
* 函 数:SPI交换传输一个字节,使用SPI模式0
* 参 数:ByteSend 要发送的一个字节
* 返 回 值:接收的一个字节
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据
{
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量
//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
}
return ByteReceive; //返回接收到的一个字节数据
}
如果对最后一段位操作有些迷糊的,可以看另一篇博文:嵌入式移位赋值操作详解。