SPI通信驱动硬件编程(1)

1、SPI协议基础

①、什么是SPI?

        SPI,全称为Serial Peripheral Interface,是一种串行外设接口。由Motorola(摩托罗拉)公司(现为NXP Semiconductors的一部分)最早推出的同步串行通信接口标准,主要用于微控制器和其他外围设备之间的高速、全双工数据交换。SPI协议允许在一个主设备(Master)和一个或多个从设备(Slave)之间建立连接主设备负责生成时钟信号并控制数据传输。当前,SPI广泛应用于各种嵌入式系统中,如SD卡、RFID、LCD屏幕、网络通信PHY以及其他需要快速可靠数据交换的器件之间。如下图所示为SPI一主多从通信示意图。

        如图可见,在多从机通信时,有3根SPI的引脚是共用的,但唯独片选引脚CS没有共用,因为通信时,只允许主机和一个从机进行通信,需要通过片选引脚进行从机设备选择

     SPI通常为四线制通信方式:

  • SCLK(Serial Clock):时钟信号,由主设备提供,决定数据传输的节拍。
  • MOSI(Master Out, Slave In):主设备输出数据,从设备输入数据引脚。
  • MISO(Master In, Slave Out):主设备输入数据,从设备输出数据引脚。
  • CS/SS(Chip Select / Slave Select):片选引脚,主设备控制,用于选择当前通信的从设备。

②、SPI工作模式

        SPI存在四种不同的工作模式,这些模式主要区别在于时钟极性和时钟相位的不同组合,其中使用的最为广泛的是模式0模式3方式。

CPOL表示高低电平顺序,叫做极性;CPHA表示第一个/第二个边沿,叫做相位

CPOL(Clock Polarity):时钟极性选择

  • CPOL=0,SPI总线空闲时,时钟线SCLK为低电平 ;
  • CPOL=1,SPI总线空闲时,时钟线SCLK为高电平。

        数据的传输是从电平跳变开始的,CPOL控制在数据传输开始前,时钟信号的电平是处于低电平0还是高电平1状态。

CPHA(Clock Phase):时钟相位选择

  • CPHA=0,在SCLK第一个跳变沿,主机对MISO引脚电平采样;主机的数据发送则在第二个跳边沿。
  • CPHA=1,在SCLK第二个跳变沿,主机对MISO引脚电平采样。主机的数据发送则在第一个跳边沿。

        一个时钟周期会有2个跳变沿。而CPHA相位,直接决定SPI总线从哪个跳变沿开始采样数据。至于这个跳变沿究竟是上升沿还是下降沿,取决于CPOL的值,CPHA只决定第几个跳变沿进行采样。

工作模式汇总:

  • CPHA=0、CPOL=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。
  • CPHA=0、CPOL=1:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,即SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
  • CPHA=1、CPOL=0:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,即SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
  • CPHA=1、CPOL=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,即SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

2、SPI通信时序图解析

①、SPI模式0

MODE0:极性0 相位0

当CPOL为低电平,CPHA为低电平时

在SCLK的上升沿时期,MISO接收数据

在SCLK的下降沿时期,MOSI发送数据

②、SPI模式1

MODE1:极性0 相位1

当CPOL为低电平,CPHA为高电平时

在SCLK的上升沿时期,MOSI发送数据

在SCLK的下降沿时期,MISO接收数据

 ③、SPI模式2

MODE2:极性1 相位0

当CPOL为高电平,CPHA为低电平时

在SCLK的上升沿时期,MOSI发送数据

在SCLK的下降沿时期,MISO接收数据

④、SPI模式3

MODE3:极性1 相位1

当CPOL为高电平,CPHA为高电平时

在SCLK的上升沿时期,MISO接收数据

在SCLK的下降沿时期,MOSI发送数据

⑤、SPI通信模式总结

MODE0:

        SCK默认为低电平

        在第一边沿(上升沿)进行读取 在第二边沿(下降沿)进行发送

MODE1:

        SCK默认为低电平

        在第一边沿(上升沿)进行发送 在第二边沿(下降沿)进行读取

MODE2:

        SCK默认为高电平

        在第一边沿(下降沿)进行读取 在第二边沿(上升沿)进行发送

MODE3:

        SCK默认为高电平

        在第一边沿(下降沿)进行发送 在第二边沿(上升沿)进行读取

3、SPI通信源码

①、编程思路分析

(1)、初始化通信GPIO引脚

(2)、确定SPI通信的具体模式

(3)、控制SPI的时钟CLK引脚

(4)、确定该模式在上升沿时刻对应的操作

(5)、确定该模式在下降沿时刻对应的操作

②、spi.h

#ifndef __SPI_H
#define __SPI_H


#define     SPI_CLK(val)	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, val)
#define     SPI_MISO		HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_2)
#define     SPI_MOSI(val)	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, val)
#define     SPI_CS(val)		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, val)


void SPI_GPIO_Init(void);
void delay(void);
char SPI_Mode0_ReadWrite_Data(char WData);
char SPI_Mode1_ReadWrite_Data(char WData);
char SPI_Mode2_ReadWrite_Data(char WData);
char SPI_Mode3_ReadWrite_Data(char WData);



#endif

③、spi.c

#include “spi.h”


/**
  * @brief  SPI GPIO引脚初始化
  * @param  NONE
  * @retval NONE
  */
void SPI_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOF_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4, GPIO_PIN_SET);
	//CLK、MOSI、CS
	GPIO_InitStruct.Pin   = GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4;
	GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull  = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	//MISO
	GPIO_InitStruct.Pin   = GPIO_PIN_2;
	GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull  = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/**
  * @brief  SPI粗延时
  * @param  NONE
  * @retval NONE
  */
void delay(void)
{
	for(int i=0; i<10;i++)
	{
		;
	}
}

/**
  * @brief  SPI模式0读写数据
  * @param  WData: 需要写的数据
  * @retval 读取的数据
  */
char SPI_Mode0_ReadWrite_Data(char WData)
{
	//模式0,SCLK默认低电平
	//上升沿读取,下降沿发送
	SPI_CLK(0);	
	char RData = 0;
	for(int i=0; i<8; i++)
	{
		delay();
		SPI_CLK(1);	//上升沿 0->1的瞬间
		RData |= SPI_MISO<<i; //接收数据
		delay();
		SPI_CLK(0);	//下降沿 1->0的瞬间
		if((WData>>i)&0x01)	//发送数据
		{
			SPI_MOSI(1);
		}else{
			SPI_MOSI(0);
		}
	}
	
	return RData;
}

/**
  * @brief  SPI模式1读写数据
  * @param  WData: 需要写的数据
  * @retval 读取的数据
  */
char SPI_Mode1_ReadWrite_Data(char WData)
{
	//模式1,SCLK默认低电平
	//上升沿发送,下降沿读取
	SPI_CLK(0);	
	char RData = 0;
	for(int i=0; i<8; i++)
	{
		delay();
		SPI_CLK(1);	//上升沿 0->1的瞬间
		if((WData>>i)&0x01)	//发送数据
		{
			SPI_MOSI(1);
		}else{
			SPI_MOSI(0);
		}
		delay();
		SPI_CLK(0);	//下降沿 1->0的瞬间
		RData |= SPI_MISO<<i; //接收数据
	}
	
	return RData;
}

/**
  * @brief  SPI模式2读写数据
  * @param  WData: 需要写的数据
  * @retval 读取的数据
  */
char SPI_Mode2_ReadWrite_Data(char WData)
{
	//模式2,SCLK默认高电平
	//下降沿读取,上升沿发送
	SPI_CLK(1);	
	char RData = 0;
	for(int i=0; i<8; i++)
	{
		delay();
		SPI_CLK(0);	//下降沿 1->0的瞬间
		RData |= SPI_MISO<<i; //接收数据
		delay();
		SPI_CLK(1);	//上升沿 0->1的瞬间
		if((WData>>i)&0x01)	//发送数据
		{
			SPI_MOSI(1);
		}else{
			SPI_MOSI(0);
		}
	}
	
	return RData;
}

/**
  * @brief  SPI模式3读写数据
  * @param  WData: 需要写的数据
  * @retval 读取的数据
  */
char SPI_Mode3_ReadWrite_Data(char WData)
{
	//模式3,SCLK默认高电平
	//下降沿发送,上升沿读取
	SPI_CLK(1);	
	char RData = 0;
	for(int i=0; i<8; i++)
	{
		delay();
		if((WData>>i)&0x01)	//发送数据
		{
			SPI_MOSI(1);
		}else{
			SPI_MOSI(0);
		}
		SPI_CLK(0);	//下降沿 1->0的瞬间
		delay();
		SPI_CLK(1);	//上升沿 0->1的瞬间
		RData |= SPI_MISO<<i; //接收数据
	}

	return RData;
}

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值