STM32之七:SPI通信

目录

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设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输的。

  1. CPOL = 1:表示空闲时是高电平
  2. CPOL = 0:表示空闲时是低电平
  3. CPHA = 1:表示从第一个跳变沿开始采样
  4. 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;								//返回接收到的一个字节数据
}

如果对最后一段位操作有些迷糊的,可以看另一篇博文:嵌入式移位赋值操作详解

以下是一个简单的STM32 SPI通信例程,用于在STM32和外部设备之间进行SPI通信。 首先,需要包含以下头文件: ```c #include "stm32f10x.h" #include "stm32f10x_spi.h" #include "stm32f10x_gpio.h" ``` 然后,需要定义SPI的GPIO引脚和SPI的配置参数: ```c #define SPI_PORT_SPI1 GPIOA #define SPI_CS_PIN GPIO_Pin_4 #define SPI_SCK_PIN GPIO_Pin_5 #define SPI_MISO_PIN GPIO_Pin_6 #define SPI_MOSI_PIN GPIO_Pin_7 #define SPI_SPEED SPI_BaudRatePrescaler_2 #define SPI_MODE SPI_Mode_Master #define SPI_DIRECTION SPI_Direction_2Lines_FullDuplex #define SPI_DATA_SIZE SPI_DataSize_8b #define SPI_FIRST_BIT SPI_FirstBit_MSB ``` 接下来,需要进行SPI的初始化: ```c void SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // Enable SPI1 and GPIOA clocks RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); // Configure SPI1 pins: SCK, MISO and MOSI GPIO_InitStructure.GPIO_Pin = SPI_SCK_PIN | SPI_MISO_PIN | SPI_MOSI_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(SPI_PORT_SPI1, &GPIO_InitStructure); // Configure SPI1 CS pin GPIO_InitStructure.GPIO_Pin = SPI_CS_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(SPI_PORT_SPI1, &GPIO_InitStructure); // Configure SPI1 SPI_InitStructure.SPI_Direction = SPI_DIRECTION; SPI_InitStructure.SPI_Mode = SPI_MODE; SPI_InitStructure.SPI_DataSize = SPI_DATA_SIZE; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_SPEED; SPI_InitStructure.SPI_FirstBit = SPI_FIRST_BIT; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); // Enable SPI1 SPI_Cmd(SPI1, ENABLE); } ``` 在SPI初始化之后,可以使用以下函数来发送和接收SPI数据: ```c uint8_t SPI_Transfer(uint8_t data) { // Wait until SPI1 is ready to send data while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // Send data SPI_I2S_SendData(SPI1, data); // Wait until data is received while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); // Receive data return SPI_I2S_ReceiveData(SPI1); } ``` 最后,可以使用以下代码来测试SPI通信: ```c int main(void) { uint8_t data = 0x55; // Initialize SPI SPI_Init(); // Select SPI chip GPIO_ResetBits(SPI_PORT_SPI1, SPI_CS_PIN); // Send data SPI_Transfer(data); // Deselect SPI chip GPIO_SetBits(SPI_PORT_SPI1, SPI_CS_PIN); while (1); } ``` 在该例程中,向SPI发送一个字节的数据,并通过SPI收到响应。在实际应用中,可能需要根据具体的需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guaizaiguaizai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值