SPI1的相关引脚中,SPI1的NSS复用在了PA4,SPI1的SCK复用在了PA5,MISO复用在了PA6,MOSI复用在了PA7,所以如果想要使用SPI1外设,就得把相应的通信线接在PA4,5,6,7这四个引脚,NSS除外我们一般可以继续使用软件模拟的方式来实现,所以NSS没有必要必须接在PA4,SPI1还可以重定义,如果SPI1原来的PA5,6,7,这些引脚正好被别的资源占用了,就可以考虑把SPI1的引脚重定义到PB3,4,5。
但这几个引脚默认情况下是作为JTAG的调试端口使用的,如果要使用它们原本的GPIO功能,或者是使用重定义的外设引脚功能,都需要先解除调式端口的复用,否则GPIO或者引脚外设都不会正常工作
我们的任务就是修改底层MySPI.c文件,把初始化和时序的执行步骤由软件实现改成硬件实现,之后基于通信层的业务代码,我们不需要进行任何修改,因为这些部分是调用底层的通信函数来实现的功能,所以我们把底层的实现,由软件改到硬件也不影响上层的代码,这就是代码隔离封装的好处
第一步,开启时钟,开启SPI和GPIO的时钟
第二步,初始化GPIO口,其中SCK和MOSI是由硬件外设控制的输出信号,所以配置为复用推完输出,MISO是硬件外设的输入信号,我们可以配置为上拉输入,因为输入设备可以有多个,所以不存在复用输入的说法,直接上拉输入就可以,普通GPIO口可以输入,外设也可以输入,最后是SS引脚,SS是软件控制的输出信号,所以配置为通用推挽输出
第三步,配置SPI外设,使用一个结构体选参数即可,调用一下SPI_Init
第四步,开关控制,调用SPI_Cmd给SPI使能即可
接下来我们看一下库函数:
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);//缺省配置
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);//初始化
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);//结构体变量初始化
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);//SPI使能
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);//DMA使能
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);//获取状态寄存器标志位,主要会用到这个获取TxE和RxNE标志位的状态
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);//清除标志位
第一步和第二步:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//输出引脚初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//SS引脚还是使用软件模拟所以可以使用通用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;//SCK和MOSI引脚是外设控制的输出要配置为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//MISO配置为上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
接下来是初始化SPI外设
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//决定当前设备是SPI主机还是从机
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//可以选择单线半双工的接收模式、单线半双工的发送模式、双线全双工、双线只接收模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//配置8位还是16位数据帧
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//选择高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//波特率分频器,配置SCK工作频率
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//时钟极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//选择软件硬件NSS模式,我们计划使用GPIO模拟这个外设NSS引脚不会用到
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC校验模式
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
MySPI_W_SS(1);//默认给SS输出高电平,默认是不选中从机的
SPI外设就绪,接下来我们就可以来完成交换字节的函数了
通常情况下就是4步,第一步等待TXE为1发送寄存器为空,如果发送寄存器不为空,我们就先不写入
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);//等待TXE为1
SPI_I2S_SendData(SPI1, ByteSend);//传入ByteSend之后,ByteSend写入到TDR,之后ByteSend自动移入移位寄存器,生成波形这个过程是自动产生的
//接收移位完成时会收到一个字节数据,这时会置标志位RXNE,所以我们只需要等待RXNE出现就行
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}
这里的两个标志位并不需要我们自己手动清除,当写入SPI_DR时,TXE标志会被清除