#STM32标准固件库的硬件SPI(NSS为软件)封装函数库

#基于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标志位也和此类似

害,一个小小的细节,纠结了我好久好久!!!。。。。。。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GuiStar_李什么恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值