SPI

1、硬件SPI初始化流程

(1)初始化通讯使用的目标引脚及端口时钟;
(2)使能 SPI外设的时钟;
(3)配置 SPI外设的模式、地址、速率等参数并使能 SPI外设;
(4)编写基本 SPI按字节收发的函数;

2、硬件SPI结构体

SPI 初始化结构体详解跟其它外设一样,STM32 标准库提供了 SPI 初始化结构体及初始化函数来配置 SPI 外设。
1 typedef struct
2 {
3 uint16_t SPI_Direction; 		/*设置 SPI 的单双向模式 */
4 uint16_t SPI_Mode; 			/*设置 SPI 的主/从机端模式 */
5 uint16_t SPI_DataSize; 		/*设置 SPI 的数据帧长度,可选 8/16 位 */
6 uint16_t SPI_CPOL; 			/*设置时钟极性 CPOL,可选高/低电平*/
7 uint16_t SPI_CPHA; 			/*设置时钟相位,可选奇/偶数边沿采样 */
8 uint16_t SPI_NSS; 			/*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
9 uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */
10 uint16_t SPI_FirstBit; 		/*设置 MSB/LSB 先行 */
11 uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */
12 } SPI_InitTypeDef;
  • (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 的时序是由通讯中的主机产生的。若被配置为从机模式,STM32的 SPI外设将接受外来的 SCK信号。
  • (3) SPI_DataSize
    本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位(SPI_DataSize_16b)
  • (4) SPI_CPOL和 SPI_CPHA
    这两个成员配置 SPI的时钟极性 CPOL和时钟相位 CPHA,这两个配置影响到 SPI的通讯模式,关于 CPOL和 CPHA 的说明参考前面“通讯模式”小节。
    时钟极性 CPOL成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。
    时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK的偶数边沿采集数据) 。
  • (5) SPI_NSS
    本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
  • (6) SPI_BaudRatePrescaler
    本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk的 2、4、6、8、16、32、64、128、256分频。
  • (7) SPI_FirstBit
    所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32的 SPI模块可以通过这个结构体成员,对这个特性编程控制。
  • (8) SPI_CRCPolynomial
    这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。
    配置完这些结构体成员后,我们要调用 SPI_Init 函数把这些参数写入到寄存器中,实现 SPI的初始化,然后调用 SPI_Cmd 来使能 SPI外设。

3、硬件SPI初始化例程

#ifndef __SPI_H
#define __SPI_H

#include "stm32f10x.h"

#define SPIx                        SPI1
#define SPI_Clock                   RCC_APB2Periph_SPI1
#define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA


/* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
#define SPI_SCK_GPIO_PORT           GPIOA
#define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
#define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
#define SPI_SCK_GPIO_PIN            GPIO_Pin_5

#define SPI_MOSI_GPIO_PORT          GPIOA
#define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
#define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz
#define SPI_MOSI_GPIO_PIN           GPIO_Pin_7

#define SPI_MISO_GPIO_PORT          GPIOA
#define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP
#define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz
#define SPI_MISO_GPIO_PIN           GPIO_Pin_6

#define SPI_NSS_GPIO_PORT          GPIOA
#define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP
#define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz
#define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致

/* 配置SPI信息 */
#define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz
#define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样
#define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
#define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓
#define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位
#define SPIx_Direction             SPI_Direction_2Lines_FullDuplex
#define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行
#define SPIx_Mode                  SPI_Mode_Master//主机模式
#define SPIx_NSS                   SPI_NSS_Soft//软件模拟

/***************************************************************************************/
#define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
#define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
#endif  /*__SPI_H*/


void SPI_Config(void)
{
	/* 初始化SPI和相对应的GPIO口 */
	SPI_InitTypeDef  SPI_InitStruct;
	GPIO_InitTypeDef GPIO_InitStruct;
	/* 打开SPI1和GPIOA的时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
	/* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
	GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;
	GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
	GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
	GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
	GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
	
	/* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */
	SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
	SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
	SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
	SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
	SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
	SPI_InitStruct.SPI_Direction = SPIx_Direction;
	SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;
	SPI_InitStruct.SPI_Mode = SPIx_Mode;
	SPI_InitStruct.SPI_NSS = SPIx_NSS;
	
	/* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
	SPI_Init(SPIx, &SPI_InitStruct);
	SPI_Cmd(SPIx, ENABLE);
	/* 拉高NSS */
	SPI_NSS_Stop();
}

}

/* 发送一个帧数据,同时接收一个帧数据 */
uint8_t SPI_SendData( uint8_t data)
{	
	uint16_t timeout=0x2710;   //10,000
	while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
		if((timeout--)==0) return printf("发送等待失败!\n");

	SPI_I2S_SendData(SPIx, data);
	timeout=0x2710;          //10,000次循环无果后为失败
	while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
		if((timeout--)==0) return printf("接收等待失败!\n");
		
	return SPI_I2S_ReceiveData(SPIx);
}


/* 读取一个帧数据 */
uint16_t SPI_ReadData(void)
{
	return SPI_SendData( 1);//此时发送的值可以为任意值
}