STM32学习笔记16-SPI硬件控制
硬件SPI
SPI外设简介
- STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
- 可配置8位/16位数据帧、高位先行/低位先行
- 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)——PCLK外设时钟
- 支持多主机模型、主或从操作
- 可精简为半双工/单工通信
- 支持DMA
- 兼容I2S协议(数字音频协议)
- STM32F103C8T6 硬件SPI资源:SPI1(挂载在APB2上,PCLK是72MHz)、SPI2(挂载在APB1上,PCLK是36MHz)
SPI框图
主设备
控制逻辑:
波特率发生器:产生SCK时钟,内部主要是一个分频器,根据PCLK来进行分频,BR0、BR1、BR2来控制分频系数
LSBFIRST:决定高位先行还是低位先行
SPE:是SPI使能。就是SPI_Cmd函数配置的位
BR:配置波特率,就是SCK时钟频率
MSTR:配置主从模式,1是主(常用),0是从
CPOL和CPHA:选择SPI的4种模式
SR状态寄存器:中的TXE发送寄存器空和RXNE接收寄存器非空
SPI基本结构
时序图
运行控制-如何产生时序,什么时候读写DR
主模式全双工连续传输
一旦TDR把数据放到移出寄存器,TEX就置1,会不断把数据放到TDR里,方便于快速传输
非连续传输-常用
会等待TDR把数据传向移位寄存器传完后使TXE置1
由于不会把数据提前放在TDR中,所以在数据传输中会有一定间隙,拖慢传输速度。
在SCK的频率越高越明显:
256分频示波图:
128分频示波图:
64分频示波图
2分频示波图
流程:
- 等待TXE为1
- 写入发送的数据至TDR
- 等待RXNE为1
- 读取RDR接收的数据
- 交换字节,重复
软件/硬件波形对比
区别与I2C一样:硬件的波形与边沿是贴合的,软件有点延迟
接线图:
引脚映射
SPI1的引脚映射:
SPI2的引脚映射:
重定义:
11-2 硬件SPI读写W25Q64——默认非连续
修改底层MySPI.c文件
- 开启时钟,SPI和GPIO时钟
- 初始化GPIO口,其中SCK和MOSI,是由硬件外设控制的输出信号,所以配置为复用推挽输出;MISO是硬件外设的输入信号,配置为上拉输入;SS引脚是软件控制的输出信号,配置为通用推挽输出
- 配置SPI外设,使用结构体
- 开关控制
- 根据时序图来执行运行控制的代码,产生交换的时序:写DR、读DR和获取状态标志位等
时序步骤:
- 等待TXE为1,发送寄存器为空
- 软件写入数据至TDR,之后转移到移位寄存器,移位寄存器会自动生产波形(这个过程是自动完成):一位一位的移到MOSI上,产生波形;由于是非连续传输,所以在这个过程中死等就行了,但是接收与发送时同时进行的,也就是接收完成了发送也完成了。
- 所以接收完成了会收到一个标志位RXNE为1
- 读取RDR,把交换接收的数据读出来
注:不需要手动清除标志位
相关库函数:
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_StructInit(SPI_InitTypeDef* SPI_I
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); //写DR数据寄存器,把数据发送到TDR
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx); //读DR数据寄存器,返回值就是接收数据寄存器RDR
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
MySPI.c
#include "stm32f10x.h" // Device header/*引脚配置层*/void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
//初始化
void MySPI_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;//SS引脚——通用推挽模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //SCK和MOSI引脚——复用推挽模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //MISO引脚——上拉输入模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Mode=SPI_Mode_Master;SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;SPI_InitStructure.SPI_CRCPolynomial=7;SPI_Init(SPI1,&SPI_InitStructure);SPI_Cmd(SPI1,ENABLE);MySPI_W_SS(1); //默认不选择从机
}
//SPI的3个时序基本单元
//
//起始
void MySPI_Start(void)
{MySPI_W_SS(0); //拉低SS,开始时序
}
//结束
void MySPI_Stop(void)
{MySPI_W_SS(1); //拉高SS,终止时序
}//
uint8_t MySPI_SwapByte(uint8_t ByteSend){while((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE))!=SET);SPI_I2S_SendData(SPI1, ByteSend);while((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE))!=SET);return SPI_I2S_ReceiveData(SPI1);
}