SPI简介
SPI协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB在布局上节省了空间。正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,它被广泛地使用在ADC、LCD、FLASH等设备与MCU之间。
中科芯CKS32F107xx系列产品拥有3路SPI接口,允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。支持以多主配置的方式工作。下面就SPI相关特性进行介绍。
SPI主要特征
SPI特征
● 3线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8或16位传输帧格式选择
● 主或从操作
● 支持多主模式
● 8个主模式波特率预分频系数(最大为fPCLK/2)
● 从模式频率(最大为fPCLK/2)
● 主模式和从模式的快速通信
● 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改
● 可编程的时钟极性和相位
● 可编程的数据顺序,MSB在前或LSB在前
● 可触发中断的专用发送和接收标志
● SPI总线忙状态标志
● 支持可靠通信的硬件CRC
─ 在发送模式下,CRC值可以被作为最后一个字节发送
─ 在全双工模式中对接收到的最后一个字节自动进行CRC校验
● 可触发中断的主模式故障、过载以及CRC错误标志
● 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求
SPI功能描述
SPI概述
SPI的方框图见下图。

图1 SPI方框图
图中的①处是SPI的引脚MOSI、MISO、SCK、NSS。CKS32F107xx系列芯片通过以上四个引脚将SPI通讯信号引出到不同GPIO引脚上,使用时必须配置到这些指定的引脚。关于GPIO引脚的复用功能可以查阅芯片数据手册。各个引脚的作用介绍如下:
●NSS:从设备选择信号线,常称为片选信号线。当有多个SPI从设备与SPI主机相连时,设备的其它信号线SCK、MOSI及MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共同只使用这3条总线;而每个从设备都有独立的一条NSS信号线,当主机要选择从设备时,把该从设备的NSS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。
●SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,两个设备之间通讯时,通讯速率受限于低速设备。
●MOSI:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
●MISO:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
图中的②处是SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCK引脚的输出时钟频率。
图中的③处是SPI的数据控制逻辑。SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及MISO、MOSI线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中,通过“数据寄存器DR”,可以获取接收缓冲区中的内容。其中数据帧的长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行还是LSB先行。
图中的④处是SPI的整体控制逻辑。整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括SPI模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。实际应用中,我们一般不使用SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出。
CKS32F107xx系列产品SPI配置:
接下来我们讲解如何利用CKS32F107xx系列固件库来完成对SPI的配置使用。跟其它外设一样,CKS32标准库提供了SPI初始化结构体及初始化函数来配置SPI外设。了解初始化结构体后我们就能对SPI外设运用自如了,结构体如下图所示:

图2 SPI配置结构体
结构体中各个函数变量的介绍及初始化时可被赋的值如下:
1) SPI_Direction:该函数设置SPI的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接收(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。
2) SPI_Mode:该函数设置SPI工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通讯中的主机产生的。若被配置为从机模式,CKS32的SPI外设将接受外来的SCK信号:
3) SPI_DataSize:该函数可以选择SPI通讯的数据帧大小是为8位(SPI_DataSize_8b)还是16位(SPI_DataSize_16b)。
4) SPI_CPOL和SPI_CPHA:这两个函数配置SPI的时钟极性CPOL和时钟相位CPHA,前面讲过这两个配置影响到SPI的通讯模式。时钟极性CPOL成员可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low)。时钟相位CPHA则可以设置为SPI_CPHA_1Edge(在SCK的奇数边沿采集数据)或SPI_CPHA_2Edge(在SCK的偶数边沿采集数据),具体组合设置方式可参考图2。

图3 数据时钟时序图
5) SPI_NSS:该函数配置NSS引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard)与软件模式(SPI_NSS_Soft),在硬件模式中的SPI片选信号由SPI硬件自动产生,而软件模式则需要我们自己把相应的GPIO端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
6) SPI_BaudRatePrescaler:该函数设置波特率分频因子,分频后的时钟即为SPI的SCK信号线的时钟频率。这个成员参数可设置为fpclk的2、4、8、16、32、64、128、256分频。可选的值如下图所示:

图4 SPI波特率分频
7) SPI_FirstBit:所有串行的通讯协议都会有MSB先行(高位数据在前)还是LSB先行(低位数据在前)的问题,而CKS32F107VET6的SPI模块可以通过这个结构体,对这个特性编程控制。

图5 SPI数据先行方式
8) SPI_CRCPolynomial:这是SPI的CRC校验中的多项式,若我们使用CRC校验时,就使用这个参数(多项式),来计算CRC的值。
配置完这些结构体成员的值,调用库函数SPI_Init即可把结构体的配置写入到寄存器中。
SPI外设主要可通过主模式和从模式两种方式进行实现,现在就两个模式分别进行讲解。
配置SPI为主模式
在主配置时,在SCK脚产生串行时钟。
配置步骤:
(1)通过SPI_CR1寄存器的BR[2:0]位定义串行时钟波特率;
(2)选择CPOL和CPHA位,定义数据传输和串行时钟间的相位关系(见图4);
(3)设置DFF位来定义8位或16位数据帧格式;
(4)配置SPI_CR1寄存器的LSBFIRST位定义帧格式;
(5)如果需要NSS引脚工作在输入模式,硬件模式下,在整个数据帧传输期间应把NSS脚连接到高电平;在软件模式下,需设置SPI_CR1寄存器的SSM位和SSI位。如果NSS引脚工作在输出模式,则只需设置SSOE位;
(6)必须设置MSTR位和SPE位(只当NSS脚被连到高电平,这些位才能保持置位)。在这个配置中,MOSI引脚是数据输出,而MISO引脚是数据输入;
数据发送过程
当写入数据至发送缓冲器时,发送过程开始。在发送第一个数据位时,数据字被并行地(通过内部总线)传入移位寄存器,而后串行地移出到MOSI脚上;MSB在先还是LSB在先,取决于SPI_CR1寄存器中的LSBFIRST位的设置。数据从发送缓冲器传输到移位寄存器时TXE标志将被置位,如果设置了SPI_CR1寄存器中的TXEIE位,将产生中断。
数据接受过程
对于接收器来说,当数据传输完成时:
●传送移位寄存器里的数据到接收缓冲器,并且RXNE标志被置位。
●如果设置了SPI_CR2寄存器中的RXNEIE位,则产生中断。在最后采样时钟沿,RXNE位被设置,在移位寄存器中接收到的数据字被传送到接收缓冲器。读SPI_DR寄存器时,SPI设备返回接收缓冲器中的数据。读SPI_DR寄存器将清除RXNE位。一旦传输开始,如果下一个将发送的数据被放进了发送缓冲器,就可以维持一个连续的传输流。在试图写发送缓冲器之前,需确认TXE标志应该为‘1’。
注:在NSS硬件模式下,从设备的NSS输入由NSS引脚控制或另一个由软件驱动的GPIO引脚控制。
具体初始化代码如下:
void SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* Configure SPI1 Pins PA.4 NSS(software); PA.5 SCK; PA.6 MISO; PA.7 MOSI--------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_WriteBit( GPIOA, GPIO_Pin_4, Bit_SET );
/* Configure SPI1 -----------------------------------------------------------*/
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStruct.SPI_Direction= SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStruct);
SPI_Cmd(SPI1, ENABLE);
}
单字节收发函数代码如下:
uint8_t CKS_SPI_Tx_Rx_Byte(uint8_t data)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, data);
while( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
配置SPI为从模式
在从模式下,SCK引脚用于接收从主设备来的串行时钟。SPI_CR1寄存器中BR[2:0]的设置不影响数据传输速率。
注:建议在主设备发送时钟之前使能SPI从设备,否则可能会发生意外的数据传输。在通信时钟的第一个边沿到来之前或正在进行的通信结束之前,从设备的数据寄存器必须就绪。在使能从设备和主设备之前,通信时钟的极性必须处于稳定的数值。请按照以下步骤配置SPI为从模式:
配置步骤
(1)设置DFF位以定义数据帧格式为8位或16位。
(2)选择CPOL和CPHA位来定义数据传输和串行时钟之间的相位关系。为保证正确的数据传输,从设备和主设备的CPOL和CPHA位必须配置成相同的方式。
(3)帧格式(SPI_CR1寄存器中的LSBFIRST位定义的“MSB在前”还是“LSB在前”)必须与主设备相同。
(4)硬件模式下(参考从选择(NSS)脚管理部分),在完整的数据帧(8位或16位)传输过程中,NSS引脚必须为低电平。在NSS软件模式下,设置SPI_CR1寄存器中的SSM位并清除SSI位。
(5)清除MSTR位、设置SPE位(SPI_CR1寄存器),使相应引脚工作于SPI模式下。在这个配置中,MOSI引脚是数据输入,MISO引脚是数据输出。
数据发送过程
在写操作中,数据字被并行地写入发送缓冲器。当从设备收到时钟信号,并且在MOSI引脚上出现第一个数据位时,发送过程开始(注:此时第一个位被发送出去)。余下的位(对于8位数据帧格式,还有7位;对于16位数据帧格式,还有15位)被装进移位寄存器。当发送缓冲器中的数据传输到移位寄存器时,SPI_SP寄存器的TXE标志被设置,如果设置了SPI_CR2寄存器的TXEIE位,将会产生中断。
数据接受过程
对于接收器,当数据接收完成时:
● 移位寄存器中的数据传送到接收缓冲器,SPI_SR寄存器中的RXNE标志被设置。
● 如果设置了SPI_CR2寄存器中的RXNEIE位,则产生中断。在最后一个采样时钟边沿后,RXNE位被置‘1’,移位寄存器中接收到的数据字节被传送到接收缓冲器。当读SPI_DR寄存器时,SPI设备返回这个接收缓冲器的数值。读SPI_DR寄存器时,RXNE位被清除。
上述部分是SPI的主/从模式理论配置过程,下面开始对实际程序中的代码配置进行分解。程序较为简单,使用单颗CKS32F107VET6的SPI1外设作为主机,SPI2外设作为从机,通过两路外设互相通信(先SPI1发,SPI2收;再SPI2发,SPI1收)的方式实现。
本讲中,我们以简单的数据接收发送为例进行配置,具体初始化代码如下:
uint8_t CKS_SPI_Tx_Rx_Byte(uint8_t data)
{
while (SPI_I2S_GetFlagStatus(SPI1,
void SPI_Slaver_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStruct;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1 | RCC_APB2Periph_AFIO, ENABLE);
/* Configure SPI1 Pins PA.4 NSS(software); PA.5 SCK; PA.6 MISO; PA.7 MOSI--------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure SPI1 -----------------------------------------------------------*/
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
SPI_InitStruct.SPI_Direction= SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Slave;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStruct);
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
SPI_Cmd(SPI1, ENABLE);
/* Configure SPI1 INT------------------------------------------------------*/
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
) == RESET);
SPI_I2S_SendData(SPI1, data);
while( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
SPI中断函数:
void SPI1_IRQHandler(void)
{
if(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) != RESET)
{
SPI_Rx = SPI_I2S_ReceiveData(SPI1);
if(SPI_Rx <= 0x04)
{
SPI_I2S_SendData(SPI1, SPI_Tx[SPI_Rx]);
}
else
{
SPI_I2S_SendData(SPI1, SPI_Tx[0x05]);
}
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}
SPI_I2S_ClearITPendingBit(SPI1, SPI_I2S_IT_RXNE);
}
}
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。