6、spi模块

串行外设接口SPI模块

spi分为主从两种模式,一个spi通讯系统要包含一个主设备和一个或多个从设备。提供时钟的是主设备。

spi的读写操作都是从主设备发起的。

SPI信号线
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

  • MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
  • SCLK:串行时钟信号,由主设备产生。
  • CS/SS:从设备片选信号(nss),由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
    当一个mcu的spi工作方式为主机时,nss为高电平;当一个mcu的spi工作方式为从机时,nss为高电平表示主机选中了当前从机。

SPI是[单主设备( single-master )]通信协议,这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”。

SPI数据发送接收
SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。

  1. 首先拉低对应SS信号线,表示与该设备进行通信
  2. 主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
    这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍
  3. 主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
  4. 从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

img

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

SPI通信有4种不同的操作模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来
控制我们主设备的通信模式,具体如下:

时钟极性(CPOL)定义了时钟空闲状态电平:

CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
时钟相位(CPHA)定义数据的采集时间。

CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。,在第2个边沿发送数据
CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。,在第1个边沿发送数据
例如:

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

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

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

Mode3:CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
img

spi模块的类型定义

typedef struct
{
  __IO uint32_t CR1;         /*!< SPI Control register 1,                              Address offset: 0x00 */
  __IO uint32_t CR2;         /*!< SPI Control register 2,                              Address offset: 0x04 */
  __IO uint32_t SR;          /*!< SPI Status register,                                 Address offset: 0x08 */
  __IO uint32_t DR;          /*!< SPI data register,                                   Address offset: 0x0C */
  __IO uint32_t CRCPR;       /*!< SPI CRC polynomial register,                         Address offset: 0x10 */
  __IO uint32_t RXCRCR;      /*!< SPI Rx CRC register,                                 Address offset: 0x14 */
  __IO uint32_t TXCRCR;      /*!< SPI Tx CRC register,                                 Address offset: 0x18 */
} SPI_TypeDef;
#include "spi.h"

#define SPI_1  0   //PTA5,PTA6,PTA7,PTA15=SPI的(SCK,MISO,MOSI,NSS)
#define SPI_2  1   //PTB13,PTB14,PTB15,PTB12=SPI的(SCK,MISO,MOSI,NSS)
#define SPI_3  2   //暂时保留

// 抽象的spi
SPI_TypeDef *SPI_ARR[] = {(SPI_TypeDef*)SPI1_BASE, (SPI_TypeDef*)SPI2_BASE, (SPI_TypeDef*)SPI3_BASE};
// spi中断类型
IRQn_Type table_irq_spi[3] = {SPI1_IRQn, SPI2_IRQn, SPI3_IRQn}; 

//=====================================================================
//文件名称:spi.c
//功能概要:spi底层驱动构件源文件
//制作单位:苏州大学嵌入式系统与物联网研究所(sumcu.suda.edu.cn)
//版    本:  2020-11-06  V2.0
//适用芯片:STM32
//=====================================================================
#include "spi.h"
SPI_TypeDef *SPI_ARR[] = {(SPI_TypeDef*)SPI1_BASE, (SPI_TypeDef*)SPI2_BASE, (SPI_TypeDef*)SPI3_BASE};
IRQn_Type table_irq_spi[3] = {SPI1_IRQn, SPI2_IRQn, SPI3_IRQn};
//=====================================================================
//函数名称:spi_init。
//功能说明:SPI初始化
//函数参数:No:模块号,可用参数可参见gec.h文件
//       MSTR:SPI主从机选择,0选择为从机,1选择为主机。
//       BaudRate:波特率,可取12000、6000、3000、1500、750、375,单位:bps
//       CPOL:CPOL=0:高有效SPI时钟(低无效);CPOL=1:低有效SPI时钟(高无效)
//       CPHA:CPHA=0相位为0; CPHA=1相位为1;
//函数返回:无
//=====================================================================
void spi_init(uint8_t No,uint8_t MSTR,uint16_t BaudRate,\
		uint8_t CPOL,uint8_t CPHA)
{
	uint32_t temp = 0x00;    //
	uint16_t Freq_div;
	uint8_t BaudRate_Mode;
	if(No<SPI_1||No>SPI_3)   No=SPI_1;    //如果SPI号参数错误则强制选择 SPI1
	//(1)使能SPI和对应GPIO时钟
	switch(No)
    {
    case SPI_1:
		RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
		RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
		//使能PTA5,PTA6,PTA7,PTA15为SPI(SCK,MISO,MOSI,NSS)功能
		GPIOA->MODER &= ~(GPIO_MODER_MODE5|GPIO_MODER_MODE6|GPIO_MODER_MODE7|GPIO_MODER_MODE15);
		GPIOA->MODER |= (GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1|GPIO_MODER_MODE15_1);
		GPIOA->AFR[0] &= ~(GPIO_AFRL_AFSEL5|GPIO_AFRL_AFSEL6|GPIO_AFRL_AFSEL7);
		GPIOA->AFR[0] |= ((GPIO_AFRL_AFSEL5_0 | GPIO_AFRL_AFSEL5_2) | (GPIO_AFRL_AFSEL6_0 | GPIO_AFRL_AFSEL6_2) | (GPIO_AFRL_AFSEL7_0 | GPIO_AFRL_AFSEL7_2));
		GPIOA->AFR[1] &= ~GPIO_AFRH_AFSEL15;
		GPIOA->AFR[1] |= (GPIO_AFRH_AFSEL15_0 | GPIO_AFRH_AFSEL15_2);
		//配置引脚速率
		GPIOA->OSPEEDR |= 0xc000fc00;
		break;
    case SPI_2:
    	//使能SPI2和GPIOB时钟
    	RCC->APB1ENR1 |= RCC_APB1ENR1_SPI2EN;
    	RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
    	//使能PTB13,PTB14,PTB15,PTB12为SPI(SCK,MISO,MOSI,NSS)功能
    	GPIOB->MODER &= ~(GPIO_MODER_MODE12|GPIO_MODER_MODE13|GPIO_MODER_MODE14|GPIO_MODER_MODE15);
    	GPIOB->MODER |= (GPIO_MODER_MODE12_1|GPIO_MODER_MODE13_1|GPIO_MODER_MODE14_1|GPIO_MODER_MODE15_1);
    	GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL12|GPIO_AFRH_AFSEL13|GPIO_AFRH_AFSEL14|GPIO_AFRH_AFSEL15);
    	GPIOB->AFR[1] |= (GPIO_AFRH_AFSEL12_0 | GPIO_AFRH_AFSEL12_2)|(GPIO_AFRH_AFSEL13_0 | GPIO_AFRH_AFSEL13_2)|(GPIO_AFRH_AFSEL14_0 | GPIO_AFRH_AFSEL14_2)|(GPIO_AFRH_AFSEL15_0 | GPIO_AFRH_AFSEL15_2);
    	//配置引脚速率
    	GPIOB->OSPEEDR |= 0xff000000;
    	break;
    case SPI_3:
    	break;
    default:
    	break;
    }

	//(2)配置CR1寄存器
	//(2.1)暂时禁用SPI功能
	SPI_ARR[No]->CR1 &= ~SPI_CR1_SPE;
	//(2.2)配置SPI主从机模式
	if(MSTR == 1)    //主机模式
	{
		temp |= SPI_CR1_MSTR;
		//配置NSS脚由软件控制,置位为1
		temp |= SPI_CR1_SSI|SPI_CR1_SSM;
	}
	else    //从机模式
	{

		temp &= ~SPI_CR1_MSTR;
		//配置NSS脚由软件控制,置位为0
		temp |= SPI_CR1_SSM;
		temp &= ~SPI_CR1_SSI;
	}

	//(2.3)配置SPI相位和极性
	if(CPOL == 1)
		temp |= SPI_CR1_CPOL;
	else
		temp &= ~SPI_CR1_CPOL;

	if(CPHA == 1)
		temp |= SPI_CR1_CPHA;
	else
		temp &= ~SPI_CR1_CPHA;

	//(2.4)配置SPI波特率
    Freq_div = SystemCoreClock/1000/BaudRate;
    BaudRate_Mode = 0;
    while(Freq_div/2 >= 2)
    {
    	BaudRate_Mode++;
    	Freq_div = Freq_div/2;
    }
    temp |= (BaudRate_Mode<<3);

    //(2.5)统一配置CR1寄存器
	SPI_ARR[No]->CR1 |= temp;
	//(3)配置CR2寄存器
	temp = 0x00;
	//(3.1)配置数据为16bit
	temp |= SPI_CR2_DS;
//	temp |= (SPI_CR2_DS_0|SPI_CR2_DS_1|SPI_CR2_DS_2);
//	temp |= SPI_CR2_FRXTH;
	SPI_ARR[No]->CR2 |= temp;
	//(4)使能SPI功能
	SPI_ARR[No]->CR1 |= SPI_CR1_SPE;

}

//=====================================================================
//函数名称:spi_send1.
//功能说明:SPI发送一字节数据。
//函数参数:No:模块号,可用参数可参见gec.h文件
//       data:     需要发送的一字节数据。
//函数返回:0:发送失败;1:发送成功。
//=====================================================================
uint8_t spi_send1(uint8_t No,uint8_t data)
{
	if(No<SPI_1||No>SPI_3)   return 0;    //如果SPI号参数错误则发送失败
	uint32_t i = 0;
	//若SPI未使能,则使能
	if ((SPI_ARR[No]->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
	{
	  SPI_ARR[No]->CR1 |= SPI_CR1_SPE;
	}
	//判断发送缓冲区是否为空。若为空,则发送数据
	while((SPI_ARR[No]->SR & SPI_SR_TXE) !=  SPI_SR_TXE)
	{
	  i++;
	  if(i>0xfffe) return 0;
	}
	SPI_ARR[No]->DR = data;
	i = 0;

	//接收回发数据,防止发送缓冲区溢出
	while((SPI_ARR[No]->SR & SPI_SR_RXNE) !=  SPI_SR_RXNE)
	{
	  i++;
	  if(i>0xfffe) return 0;
	}
	//读一次DR,SR,防止DR,SR不被清空
	do{
		volatile uint32_t tmpreg_ovr = 0x00U;
		tmpreg_ovr = SPI_ARR[No]->DR;
		tmpreg_ovr = SPI_ARR[No]->SR;
		(void)tmpreg_ovr;
	} while(0U);

    return 1;
}

//=====================================================================
//函数名称:spi_sendN
//功能说明:SPI发送数据。
//函数参数:No:模块号,可用参数可参见gec.h文件
//       n:     要发送的字节个数。范围为(1~255)
//       data[]:所发数组的首地址。
//函数返回:无。
//=====================================================================
uint8_t spi_sendN(uint8_t No,uint8_t n,uint8_t data[])
{
	if(No<SPI_1||No>SPI_3)   return 0;    //如果SPI号参数错误则发送失败
	uint32_t i;
    for (i = 0; i < n; i++)
    {
        if (!spi_send1(No, data[i])) //发送一个字节数据,失败则跳出循环
        {
            break;
        }
    }
    if(i<n)
        return 0;    //发送出错
    else
        return 1;    //发送成功
}

//=====================================================================
//函数名称:spi_receive1.
//功能说明:SPI接收一个字节的数据
//函数参数:No:模块号,可用参数可参见gec.h文件
//函数返回:接收到的数据。
//=====================================================================
uint8_t spi_receive1(uint8_t No)
{
	if(No<SPI_1||No>SPI_3)   return 0xff;    //如果SPI号参数错误则发送失败
	uint32_t i = 0;
	//若SPI未使能,则使能
	if ((SPI_ARR[No]->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
	{
	  SPI_ARR[No]->CR1 |= SPI_CR1_SPE;
	}

	//判断接收缓冲区是否不为空。若不为空,则接收数据
	while((SPI_ARR[No]->SR & SPI_SR_RXNE) !=  SPI_SR_RXNE)
	{
	  i++;
	  if(i>0xfffe) return 0xff;
	}
    return SPI_ARR[No]->DR;

}

//=====================================================================
//函数名称:spi_receiveN.
//功能说明:SPI接收数据。当n=1时,就是接受一个字节的数据……
//函数参数:No:模块号,可用参数可参见gec.h文件
//        n:    要发送的字节个数。范围为(1~255),
//       data[]:接收到的数据存放的首地址。
//函数返回:1:接收成功,其他情况:失败。
//=====================================================================
uint8_t spi_receiveN(uint8_t No,uint8_t n,uint8_t data[])
{
	if(No<SPI_1||No>SPI_3)   return 0;    //如果SPI号参数错误则发送失败
	uint32_t i;
    for (i = 0; i < n; i++)
    {
    	data[i] = spi_receive1(No);
        if (!data[i]) //发送一个字节数据,失败则跳出循环
        {
            break;
        }
    }
    if(i<n)
        return 0;    //接收出错
    else
        return 1;    //接收成功
}

//=====================================================================
//函数名称:spi_enable_re_int
//功能说明:打开SPI接收中断。
//函数参数:No:模块号,可用参数可参见gec.h文件
//函数返回:无。
//=====================================================================
void spi_enable_re_int(uint8_t No)
{
	if(No<SPI_1||No>SPI_3)   return;    //如果SPI号参数错误则发送失败
	SPI_ARR[No]->CR2 |= SPI_CR2_RXNEIE;    //开放SPI接收中断
	NVIC_EnableIRQ(table_irq_spi[No]);    //开中断控制器IRQ中断
}

//=====================================================================
//函数名称:spi_disable_re_int
//功能说明:关闭SPI接收中断。
//函数参数:No:模块号,可用参数可参见gec.h文件
//函数返回:无。
//=====================================================================
void spi_disable_re_int(uint8_t No)
{
	if(No<SPI_1||No>SPI_3)   return;   //如果SPI号参数错误则发送失败
	SPI_ARR[No]->CR2 &= ~SPI_CR2_RXNEIE;    //禁用SPI接收中断
	NVIC_DisableIRQ(table_irq_spi[No]);    //禁止中断控制器IRQ中断
}

posted @ 2023-12-09 15:10  深渊之巅  阅读(162)  评论(0编辑  收藏  举报