MCU微课堂 | CKS32F107XX系列串行外设接口介绍

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.png

图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.png

图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.png

图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.png

图4 SPI波特率分频

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

5.png

图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)。