单片机(STM32-SPI通信)

发布于:2025-07-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、概念

定义

SPI(Serial Peripheral Interface,串行外设接口)是一种常用的同步串行全双工通信协议,用于微控制器与各种外设(如传感器、存储器、显示屏等)之间的高速数据交换。

特点:

主从结构:SPI总线由一个主机(Master)控制,主机负责产生时钟(SCK),并通过片选(CS/SS)选择某个从机(Slave)进行通信。

时钟源:只有主机能产生时钟信号,从机没有时钟输出能力,不能主动发起通信。

数据流向:通信时,主机和被选中的从机之间可以双向传输数据(MOSI、MISO),但从机之间没有直接的数据通道。

在SPI通信中主机就好像皇帝,从机就是仆人,只有主机才能选择从机主动发消息,从机只能和主机通信,从机也不能主动和从机通信,只能被动响应“皇帝”的号召

二、SPI结构图

SCKL:

作用:串行时钟线,由主机产生的时钟信号,用于同步数据的发送和接收

说明:没有一个SCKL的时钟周期,MOSI和MISO线上各传输一位数据。数据的采样和发送都依赖于SCKL的时序。

MOSI:

作用:主机输出数据,数据发送数据到从机的信号线

说明:主机通过MOSI线把数据传给被选中的从机

MISO:

作用:从从机发送数据到主机的信号线。

说明:主机通过MISO线从被选中的从机读取数据。比如主机要读传感器数据,就是通过MISO线接收

SSX:

作用:是片选信号,主机用来选择具体的从机(低电平有效)。

说明:每个从机通常有独立的SSX线,主机拉低对应的SSX,选中某个从机进行通信。

三、SPI通信时序图

1.SPI通信基础流程

就是使用SPI通信协议的主从机在通信过程中随时间四个线电平高低的变化。

(1).选择从机:NSS是片选信号,主机(皇帝)翻牌子,选择从机(仆人)进行通信,把电平拉低。

(2).触发就是让时钟SCK开始改变,在变化的过程中,就是向MOSI线输出数据,所以时钟变化是数据开始出入到总线上的标志。.

(3).采样就是让时钟线SCK电平再次变化,然后从机从MOSI线里取出数据,时钟线电平再次恢复到初始电平是从机采集数据的标志。

(4).重复第二步和第三步,直到数据发送完毕,NSS拉回初始电平,这里是高电平,表示输出结束(通信结束)。

2.SPI的四种通信模式

2.1 概念:

四种通信模式就是依靠两个最重要的设置,上面拿出来举例的时序图就是其中一种。依靠时钟极性(CPOL)时钟相位(CPHA)衍生出四种通信模式。

时钟极性CPOL : 设置时钟空闲时的电平

当CPOL = 0 ,SCK引脚在空闲状态保持低电平;

当CPOL = 1 ,SCK引脚在空闲状态保持高电平。

时钟相位CPHA :设置数据采样时的时钟沿

当 CPHA=0 时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样

当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

2.2 四中通信模式:

这个图是手册中的四中时序图,比较简洁,是当CPOL是1还是0选择上面或者下面的时钟线,当CPHA是1还是0的时候选择下面的图为例,最好先把下面简单的理清楚再来消化这个图。

这四个图分开展现更清晰

模式一:

模式二:

模式三:

模式四:

注意:有这四种模式,要求主机和从机处在同一个模式才能正常接收或者发送消息,就好比主机在时钟为一的时候发,从机在时钟为一的时候取,从机能取到数据吗,主机还在发数据,数据还不稳定,很容易出错,明显是不行的。

四、数码管(SPI通信经典案例)

外设的数码管一般是四位:

1.数码管的SPI总线:

 2.SPI总线信号的滤波和上拉/上拉电阻电路:

3.四位数码管原理图:

直接看很抽象,现在我把他们连起来,知道他们在整个通信中所处于的位置就好理解了。

4.总结图: 

连上线就很明确了第一个SPI连接单片机四个引脚,分别是时钟线,片选线和输入输出线,输入的数据会有一部分传给第二个SPI,因为第二个SPI是控制哪个数码管显示,第一个SPI控制显示的数字。在代码中我们是发送一个有两个数的数组,第一个数组的数据会被挤给第二个SPI,就是第一个SPI不管这个数据直接给第二个SPI,第二个数据是第一个SPI需要处理的,这样就实现对二极管的控制,所以我们发送给这样一个数组只能控制一个灯显示数据。(第二个SPI控制数码管亮灭的连线没有练完,连上不好看就算了,理解的时候不要以为没有)

五、实现数码管的数据显示:

1.配置引脚

选择引脚:

设置通信模式和引脚类型为SPI2

2.程序编写

2.1 显示一个数码管

数码管显示数据的数组:

//定义1个保存了承有数字显示状态的数组,显示:0,1,2,3,4,5,6,7,8,9
const uint8_t number1[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; 

控制第几个数码管显示的数组:

//定义1个保存了第几个数码管进行显示状态的数组,显示第一个,显示第二个,显示第三个,显示第四个
const uint8_t number2[]={0x01,0x02,0x04,0x08}; 

将上面添加到特定位置后,在while内编写代码;代码中马上停止输出高电平不影响,因为这是在while循环内,循环速度很快,肉眼捕捉不到闪烁的现象,要是加上长时间的延时才会发生明显闪烁现象。

2.2 显示四个数码管

还可以编写一个函数,只需要在while内修改show_number数值后(数值为十六进制四位数,例如:0x4562显示4562),把show_number当做数据传入数组就可以显示四个数码管。

//定义一个保存了所有数字显示状态的数组,显示:0、1、2、3、4、5、6、7、8、9
const uint8_t number[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; 

//定义一个用于保存想要显示的数字的变量,volatile使变量不被优化,每次都刷新读取
volatile uint16_t show_number = 0x1234;

//数码管显示函数
void led_dispaly()
{
	//创建一个用于保存段选与位选的数组
	uint8_t data[2] = {0x00, 0x00};	//位置,数据	
	
	//使用switch进行判断,由于一次只能点亮一个,需要引入一个变量进行自增,循环对应各数码管
	static uint8_t num = 0;		//用于位循环
	
	switch(num)
	{
		case 0:
			data[0] = 0x08;	//1-4位顺序为 左0x01	0x02	0x04	0x08右
			data[1] = number[show_number & 0x000F];	//将想要显示的数字与之相&,就相当于盖上了其他位
		
			//SPI传输函数,参数为使用的SPI通道、要传输的数据、数据长度、超时时间
			HAL_SPI_Transmit(&hspi2,data,2,10);
		
			//进行锁存操作,等效于写入一高一低电平
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
			HAL_Delay(1);
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);//片选线
			num++;	//移动至下一位
		break;
		case 1:
			data[0] = 0x04;	//1-4位顺序为 0x01	0x02	0x04	0x08
			data[1] = number[show_number>>4 & 0x000F];	// >>4	取第三位

			//SPI传输函数,参数为使用的SPI通道、要传输的数据、数据长度、超时时间
			HAL_SPI_Transmit(&hspi2,data,2,10);
			//进行锁存操作,等效于写入一高一低电平
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
			HAL_Delay(1);
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);
			num++;	//移动至下一位
		break;
		case 2:
			data[0] = 0x02;	//1-4位顺序为 0x01	0x02	0x04	0x08
			data[1] = number[show_number>>8 & 0x000F];
			//SPI传输函数,参数为使用的SPI通道、要传输的数据、数据长度、超时时间
			HAL_SPI_Transmit(&hspi2,data,2,10);		
			//进行锁存操作,等效于写入一高一低电平
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
			HAL_Delay(1);
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);
			num++;	//移动至下一位
		break;
		case 3:
			data[0] = 0x01;//1-4位顺序为 0x01	0x02	0x04	0x08
			data[1] = number[show_number>>12 & 0x000F];	
			//SPI传输函数,参数为使用的SPI通道、要传输的数据、数据长度、超时时间
			HAL_SPI_Transmit(&hspi2,data,2,10);	
			//进行锁存操作,等效于写入一高一低电平
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
			HAL_Delay(1);
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);
			num = 0;	//移动循环
		break;
	}	
}

函数写法不唯一,完全可以自己去写一个属于自己的函数


网站公告

今日签到

点亮在社区的每一天
去签到