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串行寄存器写入一个字节来发起一次传输。
- 首先拉低对应SS信号线,表示与该设备进行通信
- 主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍 - 主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
- 从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。
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由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
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中断
}