首页 > 其他分享 >6、spi模块

6、spi模块

时间:2023-12-09 15:23:29浏览次数:31  
标签:CR1 MODER SPI No spi 模块 GPIO

串行外设接口SPI模块

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

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

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

  • MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
  • SCLK:串行时钟信号,由主设备产生。
  • CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

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中断
}

标签:CR1,MODER,SPI,No,spi,模块,GPIO
From: https://www.cnblogs.com/zk6696/p/17890998.html

相关文章

  • pip 导入导出模块 requirement.txt
    前言全局说明pip导出模块名功能,是为了在其他环境安装方便而产生的。不管是新环境部署,还给他人代码运行,这个都非常实用。一、导出pipfreeze>requirement.txt二、导入模块pipinstall-rrequirements.txt三、导入模块,指定国内安装源doubanpipinstall-rrequirem......
  • Python 输入输出与文件处理: io、pickle、json、csv、os.path 模块详解
    Python提供了强大的输入输出和文件处理工具,通过io、pickle和json等模块,开发者可以轻松处理文件、序列化和反序列化数据,并在不同格式之间进行转换。在本文中,我们将深入介绍这些模块的用法和实际示例。1.io模块:强大的输入输出工具io模块提供了对文件I/O进行灵活处理的能力......
  • Python 输入输出与文件处理: io、pickle、json、csv、os.path 模块详解
    Python提供了强大的输入输出和文件处理工具,通过io、pickle和json等模块,开发者可以轻松处理文件、序列化和反序列化数据,并在不同格式之间进行转换。在本文中,我们将深入介绍这些模块的用法和实际示例。1.io模块:强大的输入输出工具io模块提供了对文件I/O进行灵活处理的能力......
  • 无涯教程-Angular7 - Materials模块
    Materials为您的项目提供了许多内置模块。autocomplete,datepicker,slider,menus,grids和toolbar等函数。要使用Materials,我们需要导入包装。Angular2也具有上述所有函数,但可以作为@angular/core模块的一部分使用。从Angular4开始,Materials模块提供了一个单独的模块@angular/mat......
  • Java ClassLoader、ContextClassLoader与SPI实现详解
    (目录)JavaClassLoaderClassLoader做什么的?​ 众所周知,Java或者其他运行在JVM(java虚拟机)上面的程序都需要最终便以为字节码,然后被JVM加载运行,那么这个加载到虚拟机的过程就是classloader类加载器所干的事情.直白一点,就是通过一个类的全限定类名称来获取描述此类......
  • AP6212 是正基科技推出一种低成本、低功耗模块其中有所有的WiFi,蓝牙和FM功能
    AP6212 是正基科技推出一种低成本、低功耗模块其中有所有的WiFi,蓝牙和FM功能。高度集成模块使网页浏览,VoIP,蓝牙耳机,FM收音机功能的可能性应用及其他应用。具有无缝漫游功能和先进安全,也可以用不同的厂商支持802.11b/g/n无线接入点的作用局域网.无线模块符合IEEE802.11B/G/N......
  • vue+spirngboot前后端数据加解密(基于AES+RSA实现)
    案例说明案例只针对post请求这里使用’Content-Type’:‘application/x-www-form-urlencoded;charset=UTF-8’;为键值对的形式(非json)AES加密数据,RAS加密AES的key实现思路前台首先请求非加密接口获取后台的公钥前台在请求前生成自己的公钥和私钥,以及AES对称加密的key使用前台......
  • day17 模块基础
    day17开始2023年12月8日周五14:16:52time模块:importtime时间戳:time.time()从1970年开始过了多少秒格式化时间:time.strftime("%Y%m%d")结构化时间:time.localtime()睡眠:time.sleepsplit()对字符串进行切割切割的结果以列表进行保存datetime模块:datetime.datetime......
  • BOSHIDA DC电源模块与节能环保的关系
    BOSHIDADC电源模块与节能环保的关系随着全球能源危机的加剧,环保节能已经成为世界各国政府和企业发展的主要方向。在电子行业中,DC电源模块的出现为环保节能做出了贡献。DC电源模块是一种电源供应器件,可将高电压转换为低电压,为电子设备提供稳定、可靠的电源。下面我们将从DC电源......
  • Python的hashlib模块
    一、什么是摘要算法1、摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)用于生成数据或文本的简短摘要或哈希值的算法。它们被广泛应用于密码学、数据完整性验证和信息检索等领域。摘要算法通过对输入数据进......