#基于STM32标准固件库的硬件SPI(NSS为软件)封装函数库:
最近来回顾之前写过的SPI_Flash,打算重写一次SPI配置文件
spi协议最大的特点大概就是全双工了,因此stm32硬件spi的读写函数也与这个特点密切相关,话不多说,上代码:
GuiStar_SPI.h
/**
****************************************************************************************
* @file GuiStar_SPI.h
* @author GuiStar-李什么恩
* @version V1.0.1
* @date 2023-1-23
* @brief SPI全双工通讯函数库
****************************************************************************************
*/
//首先,复习一下SPI和stm32的硬件SPI
//(1)stm32硬件SPI的时钟默认是当前所在的总线(APB1或APB2)时钟的二分之一
//(2)SPI时钟极性决定空闲状态是低电平还是高电平
//(3)时钟极性:CPOL=1:时钟空闲状态为高电平;CPOL=0表示时钟空闲状态为低电平
//(4)时钟相位:CPHA=1:表示在奇数边缘采样;CPHA=0:表示在偶数边缘采样
//(5)至于采样是在时钟上升沿还是下降沿由相位和极性共同决定,所以纠结它没有意义
#ifndef __GUISTAR_SPI_H__
#define __GUISTAR_SPI_H__
#include "stm32f10x.h" // Device header
#include "IO.h"
void GuiStar_SPI_Init
(
SPI_TypeDef* SPIx,
GPIO_TypeDef* NSS_Port,
uint16_t NSS_Pin,
uint16_t SPI_DataSize,
uint16_t SPI_CPOL,
uint16_t SPI_CPHA
);
void GuiStar_SPI_Start(void);
void GuiStar_SPI_Stop(void);
void GuiStar_SPI__SendByte(u8 byte);
uint8_t GuiStar_SPI__ReadByte(void);
void GuiStar_SPI__SendTwoByte(u16 TwoBytebyte);
uint16_t GuiStar_SPI__ReadTwoByte(void);
#endif
GuiStar_SPI.c
#include "GuiStar_SPI.h"
#define GuiStar_NSS(x) GPIO_WriteBit(nss_Port, nss_Pin, (BitAction) (x))//片选线控制宏
SPI_TypeDef* spix;
GPIO_TypeDef* nss_Port;
uint16_t nss_Pin;
/**
* @brief SPI初始化函数
* @param SPIx 选择SPI(可选SPI1,SPI2,SPI3)
* @param NSS_Port 选择NSS片选引脚的端口
* @param NSS_Pin 选择NSS片选引脚的引脚
* @param SPI_DataSize 选择数据长度(可选SPI_DataSize_8b和SPI_DataSize_16b)
* @param SPI_CPOL 选择SPI极性(可选SPI_CPOL_Low和SPI_CPOL_High)
* 所谓极性,就是空闲状态下SPI时钟线的状态(高低电平)
* @param SPI_CPHA 选择SPI相位(可选SPI_CPOL_Low和SPI_CPOL_High)
* 所谓相位,就是SPI在数据线的低奇数个边沿采样还是第偶数个边沿采样
* @note (1)关于SPI的时钟:时钟频率是SPIx所在总线时钟频率的二分之一,本函数不可
* 更改(因为一般都很快所以没必要改)
* (2)SPI的SCK,MISO,MOSI引脚如下:
* SPI1_SCLK A5
* SPI1_MISO A6
* SPI1_MOSI A7
*
* SPI2_SCLK B13
* SPI2_MISO B14
* SPI2_MOSI B15
*
* SPI3_SCLK B3
* SPI3_MISO B4
* SPI3_MOSI B5
* @retval
*/
void GuiStar_SPI_Init
(
SPI_TypeDef* SPIx,
GPIO_TypeDef* NSS_Port,
uint16_t NSS_Pin,
uint16_t SPI_DataSize,
uint16_t SPI_CPOL,
uint16_t SPI_CPHA
)
{
//全局传参
spix=SPIx;
nss_Pin=NSS_Pin;
nss_Port=NSS_Port;
SPI_InitTypeDef SPI_InitStructure;
IO_Init(NSS_Port,NSS_Pin,GPIO_Mode_Out_PP);
GuiStar_NSS(1);
if(SPIx==SPI1)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
IO_Init(GPIOA,GPIO_Pin_5,GPIO_Mode_AF_PP); //SCLK
IO_Init(GPIOA,GPIO_Pin_6,GPIO_Mode_IN_FLOATING);//MISO
IO_Init(GPIOA,GPIO_Pin_7,GPIO_Mode_AF_PP); //MOSI
}
else if(SPIx==SPI2)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
IO_Init(GPIOB,GPIO_Pin_13,GPIO_Mode_AF_PP); //SCLK
IO_Init(GPIOB,GPIO_Pin_14,GPIO_Mode_IN_FLOATING);//MISO
IO_Init(GPIOB,GPIO_Pin_15,GPIO_Mode_AF_PP); //MOSI
}
else if(SPIx==SPI3)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3,ENABLE);
IO_Init(GPIOB,GPIO_Pin_3,GPIO_Mode_AF_PP); //SCLK
IO_Init(GPIOB,GPIO_Pin_4,GPIO_Mode_IN_FLOATING); //MISO
IO_Init(GPIOB,GPIO_Pin_5,GPIO_Mode_AF_PP); //MOSI
}
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //选择SPI为双向双线全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //选择SPI主从模式为主模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize; //每次发送的数据长度
SPI_InitStructure.SPI_CPOL = SPI_CPOL; //极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA; //相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //设置片选为软件模拟(NSS线为普通的IO口)
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //时钟频率的分频系数
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //校验(不用掌握)
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPIx , &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(SPIx , ENABLE);
}
/**
* @brief 开始通讯(即拉低片选引脚)
* @param 无
* @retval 无
*/
void GuiStar_SPI_Start(void)
{
GuiStar_NSS(0);
}
/**
* @brief 结束通讯(即抬高片选引脚)
* @param 无
* @retval 无
*/
void GuiStar_SPI_Stop(void)
{
GuiStar_NSS(1);
}
/**
* @brief stm32向从机发送两个字节(初始化参数SPI_DataSize选择8b的情况下使用)
* @param 要发送的一个字节(即16位数据)
* @retval 无
*/
void GuiStar_SPI__SendByte(u8 byte)
{
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_TXE) == RESET)
{
}
SPI_I2S_SendData(spix , byte);
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_RXNE) == RESET)
{
}
SPI_I2S_ReceiveData(spix);
}
/**
* @brief stm32向从机读取一个字节(初始化参数SPI_DataSize选择8b的情况下使用)
* @param 无
* @retval 读取到的一个字节(即16位数据)
*/
uint8_t GuiStar_SPI__ReadByte(void)
{
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_TXE) == RESET)
{
}
SPI_I2S_SendData(spix , 0xFF);
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_RXNE) == RESET)
{
}
return SPI_I2S_ReceiveData(spix);
}
/**
* @brief stm32向从机发送两个字节(初始化参数SPI_DataSize选择16b的情况下使用)
* @param 要发送的两个字节(即16位数据)
* @retval 无
*/
void GuiStar_SPI__SendTwoByte(u16 TwoBytebyte)
{
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_TXE) == RESET)
{
}
SPI_I2S_SendData(spix , TwoBytebyte);
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_RXNE) == RESET)
{
}
SPI_I2S_ReceiveData(spix);
}
/**
* @brief stm32向从机读取两个字节(初始化参数SPI_DataSize选择16b的情况下使用)
* @param 无
* @retval 读取到的两个字节(即16位数据)
*/
uint16_t GuiStar_SPI__ReadTwoByte(void)
{
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_TXE) == RESET)
{
}
SPI_I2S_SendData(spix , 0xFFFF);
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_RXNE) == RESET)
{
}
return SPI_I2S_ReceiveData(spix);
}
来补一个坑!!!:
非常需要注意的一点是SPI写函数GuiStar_SPI__SendByte,来看一下函数体:
/**
* @brief stm32向从机发送两个字节(初始化参数SPI_DataSize选择8b的情况下使用)
* @param 要发送的一个字节(即16位数据)
* @retval 无
*/
void GuiStar_SPI__SendByte(u8 byte)
{
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_TXE) == RESET)
{
}
SPI_I2S_SendData(spix , byte);
while (SPI_I2S_GetFlagStatus(spix , SPI_I2S_FLAG_RXNE) == RESET)
{
}
SPI_I2S_ReceiveData(spix);
}
直接看最后一句
SPI_I2S_ReceiveData(spix);
这一句大家可能觉得没什么用,毕竟现在是发送嘛,调用接收库函数没有用把???我刚开始也没有写,但是实测发现,如果不写,那么SPI_I2S_FLAG_RXNE标志位将不会被清零,那么下一次再调用此函数或者调用读取函数,就不会再等待接收数据寄存器全部接收到数据,直接结束函数,那么读取到的数据肯定有问题!!也就是说,检测完标志位之后,必须有清零标志位这一个步骤,否则下次检测该标志位就会直接认为标志位符合条件了。那肯定会出问题,如何清零
stm32参考手册上说明了:RXNE标志位和TXE标志位分别在访问数据或写数据的时候可以被清零(也就是调用读取和写入的库函数),除此之外,串口协议的TXE和RXNE标志位也和此类似!
害,一个小小的细节,纠结了我好久好久!!!。。。。。。