认识一下SPI?
SPI的全称是"Serial Peripheral Interface",意为串行外围接口,SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。
下面是SPI结构简易图
上图我们看到SPI的主要4根线
MISO: 总线(主设备入从设备出)主入从出
MOSI:
SCLK: 时钟信号线,用于通信数据同步,决定通信速率
CS: 通常片选信号(从设备的选择信号线,cs 数量由从设备数量决定,低电平开始通信)
了解SPI 特点
SPI 主要特点有: 可以同时发出和接收串行数据; 可以当作主机或从机工作; 提供频率可编程时钟; 发送结束中断标志; 写冲突保护; 总线竞争保护等。
SPI 2大主要功能
STM32F1的SPI 接口提供两个主要功能,支持 SPI 协议或 I2S 音频协议。STM32F1的SPI时钟最高可以到 36MHz,支持 DMA功能
SPI 总线有四种工作方式
(按照通过时钟极性( CPOL)和时钟相位( CPHA)区分)
SPI总线有四种工作方式,其中使用的最为广泛的是SPI0和SPI3方式(实线表示):
四种工作方式时序
时序详解:
CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样
工作方式1:
当CPHA=0、CPOL=0时SPI总线工作在方式1。MISO引脚上的数据在第一个SPSCK沿跳变之前已经上线了,而为了保证正确传输,MOSI引脚的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,首先将数据上线,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
工作方式2:
当CPHA=0、CPOL=1时SPI总线工作在方式2。与前者唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。
工作方式3:
当CPHA=1、CPOL=0时SPI总线工作在方式3。MISO引脚和MOSI引脚上的数据的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,在同步时钟信号周期开始时(上升沿)数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
工作方式4:
当CPHA=1、CPOL=1时SPI总线工作在方式4。与前者唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。
SPI配置步骤
具体步骤如下:(SPI相关库函数在stm32f10x_spi.c和
stm32f10x_spi.h文件中)
(1)使能SPI及对应GPIO端口时钟并配置引脚的复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
(2)初始化SPI,包括数据帧长度、传输模式、MSB和LSB顺序等
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
typedef struct
{
uint16_t SPI_Direction; //设置SPI的单双向模式
uint16_t SPI_Mode; //设置 SPI 的主/从机端模式
uint16_t SPI_DataSize; //设置 SPI 的数据帧长度,可选 8/16 位
uint16_t SPI_CPOL; //设置时钟极性 CPOL,可选高/低电平
uint16_t SPI_CPHA; //设置时钟相位,可选奇/偶数边沿采样
uint16_t SPI_NSS; //设置 NSS 引脚由 SPI 硬件控制还是软件控制
uint16_t SPI_BaudRatePrescaler;//设置时钟分频因子
uint16_t SPI_FirstBit; //设置 MSB/LSB 顺序
uint16_t SPI_CRCPolynomial; //设置 CRC 校验的表达式
}SPI_InitTypeDef;
SPI_Direction:用于设置 SPI 的通信方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接收(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。
SPI_Mode:用于设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
SPI_DataSize:用于设置SPI通信的数据帧长度,可以选择8位(SPI_DataSize_8b)或者 16 位(SPI_DataSize_16b)。
SPI_CPOL:用于设置时钟极性,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。
SPI_CPHA:用于设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
SPI_NSS:用于设置NSS引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 NSS信号由 SPI 硬件自动产生,而软件模式则需要我们使用相应的 GPIO 端口来控制。
SPI_BaudRatePrescaler:用于设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
SPI_FirstBit:用于设置数据传输顺序是 MSB 位在前还是 LSB 位在前。
SPI_CRCPolynomial:用于设置 CRC 校验多项式,提高通信可靠性。
(3)使能(开启)SPI
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
(4)SPI数据传输
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
(5)查看SPI传输状态
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
使用较多的是发送完成标志(SPI_I2S_FLAG_TXE)和接收完成标志(SPI_I2S_FLAG_RXNE)
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE);
//本次使用stm32F103 zet6 控制板
spi 函数
void SPI2_Init(void); //初始化SPI2口
void SPI2_SetSpeed(u8 SpeedSet); //设置SPI2速度
u8 SPI2_ReadWriteByte(u8 TxData);//SPI2总线读写一个字节
//SPI口初始化
//这里针是对SPI2的初始化
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
/* SPI的IO口和SPI外设打开时钟 */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/* SPI的IO口设置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
//SPI2速度设置函数
//SPI速度=fAPB1/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB1时钟一般为36Mhz:
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
SPI2->CR1&=0XFFC7;//位3-5清零,用来设置波特率
SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI速度
SPI_Cmd(SPI2,ENABLE); //使能SPI2
}
//SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);//等待发送区空
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个byte 数据
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一个byte
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
注释flash 存储模块根据不同的产品对应编写不同的flash 代码